Глава 6. Типы
Оглавление-
6.1. Типы значений
- 6.1.1. Основные типы значений
- 6.1.2. Составные типы
- 6.1.3. Типы коллекций
- 6.2. Типы сущностей
- 6.3. Значение категорий типов
- 6.4. Пользовательские типы
- 6.5. Реестр типов
В качестве решения объектного/реляционного отображения Hibernate имеет дело с представлением
данных приложений Java и JDBC. Например, приложение онлайн-каталога имеет, скорее всего, объект
Product
с рядом атрибутов, таких как sku
, name
и т. д. Для этих отдельных атрибутов Hibernate должен иметь возможность считывать значения
из базы данных и записывать их обратно. Эта «сортировка» (marshalling)
является функцией типа Hibernate, которая представляет собой реализацию интерфейса
org.hibernate.type.Type
. Кроме того, тип Hibernate описывает различные аспекты поведения
типа Java, такие как «как проверяется равенство?» или «как клонируются значения?».
Важно
Тип Hibernate не является ни Java-типом, ни типом данных SQL; он предоставляет информацию об обоих. Когда вы сталкиваетесь с термином тип в отношении Hibernate, имейте в виду, что он может подразумевать как тип Java, так и тип SQL/JDBC или тип Hibernate.
Hibernate классифицирует типы на две группы высокого уровня: типы значений (см. раздел «6.1 Типы значений») и типы сущностей (см. раздел «6.2 Типы сущностей»).
6.1. Типы значений
Основной отличительной характеристикой типов значения является тот факт, что они не определяют свой собственный жизненный цикл. Мы говорим, что они «принадлежат» чему-то другому (в частности, сущности, как мы увидим позже), которая определяет их жизненный цикл. Типы значений далее подразделяются на три подкатегории: основные типы (см. раздел «6.1.1 Основные типы значений»), составные типы (см. раздел «6.1.2 Составные типы») и типы коллекций (см. раздел «6.1.3 Типы коллекций»).
6.1.1. Основные типы значений
Нормой для основных типов значений является то, что они сопоставляют одно значение базы данных (столбец) с одним, неагрегированным Java-типом. Hibernate предоставляет ряд встроенных базовых типов, которые мы представим в следующих разделах по типу Java. В основном они следуют естественным сопоставлениям, рекомендованным в спецификации JDBC. В дальнейшем мы рассмотрим, как переопределить эти сопоставления и как предоставить и использовать сопоставления альтернативных типов.
6.1.1.1. java.lang.String
- org.hibernate.type.StringType
-
Отображает строку в тип VARCHAR JDBC. Это стандартное сопоставление для строки,
если не указан другой тип Hibernate.
Зарегистрирован как
string
иjava.lang.String
в реестре типов (см. раздел «6.5 Реестр типов»). - org.hibernate.type.MaterializedClob
-
Отображает строку в тип CLOB JDBC.
Зарегистрирован как
materialized_clob
в реестре типов (см. раздел «6.5 Реестр типов»). - org.hibernate.type.TextType
-
Отображает строку в тип LONGVARCHAR JDBC.
Зарегистрирован как
text
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.2. java.lang.Character (или примитив char)
- org.hibernate.type.CharacterType
-
Отображает char в тип CHAR JDBC.
Зарегистрирован как
char
иjava.lang.Character
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.3. java.lang.Boolean (или примитив boolean)
- org.hibernate.type.BooleanType
-
Отображает boolean в тип BIT JDBC.
Зарегистрирован как
boolean
иjava.lang.Boolean
в реестре типов (см. раздел «6.5 Реестр типов»). - org.hibernate.type.NumericBooleanType
-
Отображает boolean в тип INTEGER JDBC как 0 = false, 1 = true.
Зарегистрирован как
numeric_boolean
в реестре типов (см. раздел «6.5 Реестр типов»). - org.hibernate.type.YesNoType
-
Отображает boolean в тип CHAR JDBC как ('N'|'n') = false, ('Y'|'y') = true.
Зарегистрирован как
yes_no
в реестре типов (см. раздел «6.5 Реестр типов»). - org.hibernate.type.TrueFalseType
-
Отображает boolean в тип CHAR JDBC как ('F'|'f') = false, ('T'|'t') = true.
Зарегистрирован как
true_false
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.4. java.lang.Byte (или примитив byte)
- org.hibernate.type.ByteType
-
Отображает byte или
java.lang.Byte
в тип TINYINT JDBC. Зарегистрирован какbyte
иjava.lang.Byte
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.5. java.lang.Short (или примитив short)
- org.hibernate.type.ShortType
-
Отображает short или
java.lang.Short
в тип SMALLINT JDBC. Зарегистрирован какshort
иjava.lang.Short
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.6. java.lang.Integer (или примитив int)
- org.hibernate.type.IntegerTypes
-
Отображает int или
java.lang.Integer
в тип INTEGER JDBC. Зарегистрирован какint
иjava.lang.Integer
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.7. java.lang.Long (или примитив long)
- org.hibernate.type.LongType
-
Отображает long или
java.lang.Long
в тип BIGINT JDBC. Зарегистрирован какlong
иjava.lang.Long
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.8. java.lang.Float (или примитив float)
- org.hibernate.type.FloatType
-
Отображает float или
java.lang.Float
в тип FLOAT JDBC. Зарегистрирован какfloat
иjava.lang.Float
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.9. java.lang.Double (или примитив double)
- org.hibernate.type.DoubleType
-
Отображает double или
java.lang.Double
в тип DOUBLE JDBC. Зарегистрирован какdouble
иjava.lang.Double
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.10. java.lang.BigInteger
- org.hibernate.type.BigIntegerType
-
Отображает
java.math.BigInteger
в тип NUMERIC JDBC. Зарегистрирован какbig_integer
иjava.math.BigInteger
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.11. java.lang.BigDecimal
- org.hibernate.type.BigDecimalType
-
Отображает
java.math.BigDecimal
в тип NUMERIC JDBC. Зарегистрирован какbig_decimal
иjava.math.BigDecimal
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.12. java.util.Date или java.sql.Timestamp
- org.hibernate.type.TimestampType
-
Отображает
java.sql.Timestamp
в тип TIMESTAMP JDBC. Зарегистрирован какtimestamp
,java.sql.Timestamp
иjava.util.Date
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.13. java.sql.Time
- org.hibernate.type.TimeType
-
Отображает
java.sql.Time
в тип TIME JDBC. Зарегистрирован какtime
иjava.sql.Time
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.14. java.sql.Date
- org.hibernate.type.DateType
-
Отображает
java.sql.Time
в тип DATE JDBC. Зарегистрирован какdate
иjava.sql.Date
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.15. java.util.Calendar
- org.hibernate.type.CalendarType
-
Отображает
java.util.Calendar
в тип TIMESTAMP JDBC. Зарегистрирован какcalendar
,java.util.Calendar
иjava.util.GregorianCalendar
в реестре типов (см. раздел «6.5 Реестр типов»). - org.hibernate.type.CalendarDateType
-
Отображает
java.util.Calendar
в тип DATE JDBC. Зарегистрирован какcalendar_date
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.16. java.util.Currency
- org.hibernate.type.CurrencyType
-
Отображает
java.util.Currency
в тип VARCHAR JDBC (с использованием кода Currency). Зарегистрирован какcurrency
иjava.util.Currency
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.17. java.util.Locale
- org.hibernate.type.LocaleType
-
Отображает
java.util.Locale
в тип VARCHAR JDBC (с использованием кода Locale). Зарегистрирован какlocale
иjava.util.Locale
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.18. java.util.TimeZone
- org.hibernate.type.TimeZoneType
-
Отображает
java.util.TimeZone
в тип VARCHAR JDBC (с использованием TimeZone ID). Зарегистрирован какtimezone
иjava.util.TimeZone
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.19. java.net.URL
- org.hibernate.type.UrlType
-
Отображает
java.net.URL
в тип VARCHAR JDBC (с использованием внешней формы). Зарегистрирован какurl
иjava.util.URL
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.20. java.lang.Class
- org.hibernate.type.ClassType
-
Отображает
java.lang.Class
в тип VARCHAR JDBC (с использованием имя Class). Зарегистрирован какclass
иjava.lang.Class
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.21. java.sql.Blob
- org.hibernate.type.BlobType
-
Отображает
java.sql.Blob
в тип BLOB JDBC. Зарегистрирован какblob
иjava.sql.Blob
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.22. java.sql.Clob
- org.hibernate.type.ClobType
-
Отображает
java.sql.Clob
в тип CLOB JDBC. Зарегистрирован какclob
иjava.sql.Clob
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.23. byte[]
- org.hibernate.type.BinaryType
-
Отображает примитив byte[] в тип VARBINARY JDBC.
Зарегистрирован как
binary
иbyte[]
в реестре типов (см. раздел «6.5 Реестр типов»). - org.hibernate.type.MaterializedBlobType
-
Отображает примитив byte[] в тип BLOB JDBC.
Зарегистрирован как
materialized_blob
в реестре типов (см. раздел «6.5 Реестр типов»). - org.hibernate.type.ImageType
-
Отображает примитив byte[] в тип LONGVARBINARY JDBC.
Зарегистрирован как
image
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.24. Byte[]
- org.hibernate.type.BinaryType
-
Отображает java.lang.Byte[] в тип VARBINARY JDBC.
Зарегистрирован как
wrapper-binary
,Byte[]
иjava.lang.Byte[]
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.25. char[]
- org.hibernate.type.CharArrayType
-
Отображает char[] в тип VARCHAR JDBC.
Зарегистрирован как
characters
,char[]
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.26. Character[]
- org.hibernate.type.CharacterArrayType
-
Отображает java.lang.Character[] в тип VARCHAR JDBC.
Зарегистрирован как
wrapper-characters
,Character[]
иjava.lang.Character[]
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.27. java.util.UUID
- org.hibernate.type.UUIDBinaryType
-
Отображает java.util.UUID в тип BINARY JDBC.
Зарегистрирован как
uuid-binary
иjava.util.UUID
в реестре типов (см. раздел «6.5 Реестр типов»). - org.hibernate.type.UUIDCharType
-
Отображает java.util.UUID в тип CHAR JDBC (хотя VARCHAR тоже подходит для существующих схем).
Зарегистрирован как
uuid-char
в реестре типов (см. раздел «6.5 Реестр типов»). - org.hibernate.type.PostgresUUIDType
-
Отображает java.util.UUID в тип данных PostgreSQL UUID (через
Types#OTHER
. Смотря как определит драйвер JDBC PostgreSQL). Зарегистрирован какpg-uuid
в реестре типов (см. раздел «6.5 Реестр типов»).
6.1.1.28. java.io.Serializable
- org.hibernate.type.SerializableType
-
Отображает имплементации java.lang.Serializable в тип VARBINARY JDBC.
В отличие от других типов значений, существует несколько экземпляров этого типа.
Он регистрируется один раз под
java.io.Serializable
. Кроме того, он регистрируется под конкретными именами классовjava.io.Serializable
.
6.1.2. Составные типы
Заметка
Java Persistence API называет их встроенные (embedded) типы, тогда как Hibernate традиционно называет их компонентами. Просто имейте в виду, что оба термина используются и означают одно и то же когда обсуждается Hibernate.
Компоненты представляют агрегацию значений в один тип Java. Например, у вас есть класс Address, который объединяет информацию о улицах, городах, штатах и т. д. Или класс Name, который объединяет части имени человека. Во многих случаях компонент выглядит точно как сущность. Они оба (вообще говоря) классы, написанные специально для приложения. Оба они могут иметь ссылки на другие классы в приложении, а также на коллекции и простые типы JDK. Как обсуждалось ранее, единственной отличительной особенностью является тот факт, что компонент не имеет собственного жизненного цикла и не определяет идентификатор.
6.1.3. Типы коллекций
Важно
Очень важно понимать, что мы имеем в виду саму коллекцию, а не ее содержимое. Содержимое коллекции, в свою очередь, может быть базовым типом, составным типом или типом сущностным (хотя и не коллекциями), но принадлежать самой коллекции.
Коллекции описаны в главе 7 «Отображение коллекции».
6.2. Типы сущностей
Определение сущностей подробно описано
в главе 4 «Постоянные классы».
В рамках текущего обсуждения достаточно сказать, что сущности являются классами (обычно специфичными
для приложения), которые коррелируют с строками в таблице. В частности, они соотносятся
с строкой с помощью уникального идентификатора. Из-за этого уникального идентификатора сущности
существуют независимо и определяют свой собственный жизненный цикл. В качестве примера,
когда мы удаляем Membership
, сущности User
и Group
сохраняются.
Заметка
Это понятие независимости сущности может быть изменено разработчиком приложения с использованием концепции каскадов. Каскады позволяют некоторым операциям продолжать выполняться (каскадом) через объединение от одной сущности к другой. Каскады подробно описаны в главе 8 «Ассоциативные отображения».
6.3. Значение категорий типов
Почему мы тратим столько времени на категоризацию различных типов? В чем смысл различия?
Основная категоризация была между типами сущностей и типами значений. Для обзора мы сказали, что сущности, в силу своего уникального идентификатора, существуют независимо от других объектов, тогда как значения нет. Приложение не может «удалить» sku у Product; вместо этого, sku удаляется, когда сам продукт удаляется (очевидно, вы можете обновить sku этого Product значением null, чтобы заставить его «уйти», но даже так доступ осуществляется через Product).
Вы также не можете определить связь с этим Product. Вы можете определить связь с продуктом на основе его sku, предполагая, что sku уникален, но это совершенно другое.
Продолжение следует ...
6.4. Пользовательские типы
Hibernate упрощает разработку разработчиками собственных типов значений. Например, вы можете
сохранить свойства типа java.lang.BigInteger
для столбцов VARCHAR
.
Пользовательские типы не ограничиваются отображением значений в один столбец таблицы.
Так, например, вы можете объединить столбцы FIRST_NAME
, INITIAL
и SURNAME
в java.lang.String
.
Существует 3 подхода к разработке пользовательского типа Hibernate. Чтобы проиллюстрировать
различные подходы, рассмотрим пример использования, когда нам нужно составить java.math.BigDecimal
и java.util.Currency
вместе в пользовательский класс Money
.
6.4.1. Основные типы с использованием org.hibernate.type.Type
Первый подход заключается в непосредственном внедрении интерфейса org.hibernate.type.Type
(или одного из его производных). Вероятно, вас больше интересует более конкретный контракт
org.hibernate.type.BasicType
, который позволит зарегистрировать тип
(см. раздел «6.5 Реестр типов»).
Преимущество этой регистрации заключается в том, что всякий раз, когда метаданные для определенного
свойства не указывают тип Hibernate для использования, Hibernate будет обращаться к реестру
для открытого типа свойства. В нашем примере тип свойства будет Money
,
являющегося ключом, который мы будем использовать для регистрации нашего типа в реестре:
Пример 6.1. Определение и регистрация пользовательского Type.
public class MoneyType implements BasicType { public String[] getRegistrationKeys() { return new String[] { Money.class.getName() }; }
public int[] sqlTypes(Mapping mapping) { // We will simply use delegation to the standard basic types for BigDecimal and Currency for many of the // Type methods... return new int[] { BigDecimalType.INSTANCE.sqlType(), CurrencyType.INSTANCE.sqlType(), }; // we could also have honored any registry overrides via... //return new int[] { // mappings.getTypeResolver().basic( BigDecimal.class.getName() ).sqlTypes( mappings )[0], // mappings.getTypeResolver().basic( Currency.class.getName() ).sqlTypes( mappings )[0] //}; }
public Class getReturnedClass() { return Money.class; }
public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws SQLException { assert names.length == 2; BigDecimal amount = BigDecimalType.INSTANCE.get( names[0] ); // already handles null check Currency currency = CurrencyType.INSTANCE.get( names[1] ); // already handles null check return amount == null && currency == null ? null : new Money( amount, currency ); }
public void nullSafeSet(PreparedStatement st, Object value, int index, boolean[] settable, SessionImplementor session) throws SQLException { if ( value == null ) { BigDecimalType.INSTANCE.set( st, null, index ); CurrencyType.INSTANCE.set( st, null, index+1 ); } else { final Money money = (Money) value; BigDecimalType.INSTANCE.set( st, money.getAmount(), index ); CurrencyType.INSTANCE.set( st, money.getCurrency(), index+1 ); } } ... }
Configuration cfg = new Configuration(); cfg.registerTypeOverride( new MoneyType() ); cfg...;
Важно
Важно, чтобы мы зарегистрировали тип перед добавлением отображений.
6.4.2. Основные типы с использованием org.hibernate.usertype.UserType
Заметка
org.hibernate.usertype.UserType
и org.hibernate.usertype.CompositeUserType
были первоначально добавлены,
чтобы изолировать код пользователя от внутренних изменений интерфейсов
org.hibernate.type.Type
.
Второй подход заключается в использовании интерфейса org.hibernate.usertype.UserType
,
который представляет несколько упрощенное представление интерфейса org.hibernate.type.Type
.
Используя org.hibernate.usertype.UserType
, наш пользовательский тип Money
будет выглядеть следующим образом:
Пример 6.2. Определение пользовательского UserType.
public class MoneyType implements UserType { public int[] sqlTypes() { return new int[] { BigDecimalType.INSTANCE.sqlType(), CurrencyType.INSTANCE.sqlType(), }; }
public Class getReturnedClass() { return Money.class; }
public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws SQLException { assert names.length == 2; BigDecimal amount = BigDecimalType.INSTANCE.get( names[0] ); // already handles null check Currency currency = CurrencyType.INSTANCE.get( names[1] ); // already handles null check return amount == null && currency == null ? null : new Money( amount, currency ); }
public void nullSafeSet(PreparedStatement st, Object value, int index) throws SQLException { if ( value == null ) { BigDecimalType.INSTANCE.set( st, null, index ); CurrencyType.INSTANCE.set( st, null, index+1 ); } else { final Money money = (Money) value; BigDecimalType.INSTANCE.set( st, money.getAmount(), index ); CurrencyType.INSTANCE.set( st, money.getCurrency(), index+1 ); } }
... }
Не существует большой разницы между примером org.hibernate.type.Type
и примером
org.hibernate.usertype.UserType
, но это только из-за показанных фрагментов.
Если вы выберете подход org.hibernate.type.Type
, то вам нужно юудет реализовать
немало методов по сравнению с org.hibernate.usertype.UserType
.
6.4.3. Основные типы с использованием org.hibernate.usertype.CompositeUserType
Третий и последний подход — это использование интерфейса
org.hibernate.usertype.CompositeUserType
, который отличается
от org.hibernate.usertype.UserType
тем, что он дает нам возможность предоставить
Hibernate информацию для обработки композиции в классе Money
(в частности,
2 атрибута). Это даст нам возможность, например, ссылаться на атрибут amount
в запросе HQL. Используя org.hibernate.usertype.CompositeUserType
, наш пользовательский
тип Money
будет выглядеть следующим образом:
Пример 6.3. Определение пользовательского CompositeUserType.
public class MoneyType implements CompositeUserType { public String[] getPropertyNames() { // ORDER IS IMPORTANT! it must match the order the columns are defined in the property mapping return new String[] { "amount", "currency" }; }
public Type[] getPropertyTypes() { return new Type[] { BigDecimalType.INSTANCE, CurrencyType.INSTANCE }; }
public Class getReturnedClass() { return Money.class; }
public Object getPropertyValue(Object component, int propertyIndex) { if ( component == null ) { return null; } final Money money = (Money) component; switch ( propertyIndex ) { case 0: { return money.getAmount(); } case 1: { return money.getCurrency(); } default: { throw new HibernateException( "Invalid property index [" + propertyIndex + "]" ); } } }
public void setPropertyValue(Object component, int propertyIndex, Object value) throws HibernateException { if ( component == null ) { return; } final Money money = (Money) component; switch ( propertyIndex ) { case 0: { money.setAmount( (BigDecimal) value ); break; } case 1: { money.setCurrency( (Currency) value ); break; } default: { throw new HibernateException( "Invalid property index [" + propertyIndex + "]" ); } } }
public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws SQLException { assert names.length == 2; BigDecimal amount = BigDecimalType.INSTANCE.get( names[0] ); // already handles null check Currency currency = CurrencyType.INSTANCE.get( names[1] ); // already handles null check return amount == null && currency == null ? null : new Money( amount, currency ); }
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws SQLException { if ( value == null ) { BigDecimalType.INSTANCE.set( st, null, index ); CurrencyType.INSTANCE.set( st, null, index+1 ); } else { final Money money = (Money) value; BigDecimalType.INSTANCE.set( st, money.getAmount(), index ); CurrencyType.INSTANCE.set( st, money.getCurrency(), index+1 ); } }
... }
6.5. Реестр типов
Внутри Hibernate использует реестр основных типов (см. раздел
«6.1.1 Основные типы значений», когда ему необходимо разрешить
конкретный тип org.hibernate.type.Type
для использования в определенных ситуациях.
Он также предоставляет возможность приложениям добавлять регистрации дополнительных базовых типов,
а также отменять стандартную регистрацию базовых типов.
Чтобы зарегистрировать новый тип или переопределить регистрацию существующего типа, приложения будут
использовать метод registerTypeOverride
класса org.hibernate.cfg.Configuration
при загрузке Hibernate. Например, вы хотите, чтобы Hibernate использовал ваш пользовательский
SuperDuperStringType
. Во время начальной загрузки нужно сделать вызов:
Пример 6.4. Переопределение стандартного StringType
Configuration cfg = ...; cfg.registerTypeOverride( new SuperDuperStringType() );
Аргумент registerTypeOverride
- это org.hibernate.type.BasicType
,
являющийся специализацией типа org.hibernate.type.Type
, который мы видели ранее.
Он добавляет один метод:
Пример 6.5. Фрагмент из BasicType.java
/** * Get the names under which this type should be registered in the type registry. * * @return The keys under which to register this type. */ public String[] getRegistrationKeys();
Один из подходов — использовать наследование (SuperDuperStringType
расширяет
org.hibernate.type.StringType
), другой — использовать делегирование.