Глава 6. Типы

Оглавление
  1. 6.1. Типы значений
    1. 6.1.1. Основные типы значений
    2. 6.1.2. Составные типы
    3. 6.1.3. Типы коллекций
  2. 6.2. Типы сущностей
  3. 6.3. Значение категорий типов
  4. 6.4. Пользовательские типы
    1. 6.4.1. Основные типы с использованием org.hibernate.type.Type
    2. 6.4.2. Основные типы с использованием org.hibernate.usertype.UserType
    3. 6.4.3. Основные типы с использованием org.hibernate.usertype.CompositeUserType
  5. 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), другой — использовать делегирование.