Маппинг примитивных типов данных
Во время предыдущих трех уровней мы немного познакомились с Hibernate. Настало время зайти на второй круг. Теперь мы начнем изучать тоже самое, только глубже. И начнем мы с маппинга полей Entity-класса на колонки таблиц в базе данных.
Как ты уже знаешь, маппинг поля в Entity классе на колонку выполняется с помощью аннотации @Column . И теперь вопрос: а поля каких типов можно замапить такой аннотацией?
Все типы данных в Java можно разбить на три группы:
- Тип достаточно простой и его легко сохранить в базу данных.
- Тип сложный и для него нужно писать специальный конвертор.
- Тип очень сложный и для хранения его значений нужна отдельная таблица.
К простым типам, про которые Hibernate знает, как их сохранять, относятся такие:
Типы в языке Java | package | Примеры классов |
---|---|---|
Примитивные типы языка Java | boolean , int , double и т. п. | |
Обертки над примитивами | java.lang | Boolean , Integer , Double и т. п. |
Строки | java.lang | String |
«Продвинутые» числа | java.math | BigInteger и BigDecimal |
Дата и время | java.time | LocalDate , LocalTime , LocalDateTime , OffsetTime , OffsetDateTime , Instant |
Различные вариации даты и времени | java.util | Date и Calendar |
Старые форматы даты и времени | java.sql | Date , Time , Timestamp |
Массив байт или символов | byte[] или Byte[] , char[] или Character[] | |
Перечисления (enum) | Любой enum | |
Сериализуемые объекты | Любая имплементация java.io.Serializable |
Для всех этих типов есть их аналоги в языке SQL, поэтому Hibernate хорошо знает, как их сохранять и загружать из базы.
@Entity @Table(name="user") class User
Задания типа вручную – аннотация @Type
Иногда ты можешь захотеть вмешаться в политику Hibernate и явно указать ему в каком типе нужно хранить данные в базе. Например, у тебя в Entity-классе поле имеет тип Integer, но в базе для него есть колонка с типом VARCHAR.
Для этого есть специальная аннотация – @Type . Выглядит она очень просто:
Давай, например, попросим Hibernate, чтобы поле createdDate нашего класса User хранилось в базе в виде строки:
@Entity @Table(name="user") class User
Если Hibernate поймет, как конвертировать тип Date в ваш новый тип, то просто сделает это. Если не поймет, тогда тебе нужно будет указать специальной конвертор типов. Но об этом немного позднее.
Список Hibernate-типов для баз данных
Кстати, ты обратил внимание, что мы указали тип org.hibernate.type.StringType, а не String. Это потому, что мы выбрали один из типов, который поддерживает СУБД, а не язык Java. У них у каждого своя система типов. Просто разработчики Hibernate придумали удобные названия в стиле Java, вместо этих VARCHAR’ов.
Кстати, этот список не такой уж и маленький. Приведу тут его часть:
Hibernate type (org.hibernate.type package) | JDBC type | Java type | BasicTypeRegistry key(s) |
---|---|---|---|
StringType | VARCHAR | java.lang.String | string, java.lang.String |
MaterializedClob | CLOB | java.lang.String | materialized_clob |
TextType | LONGVARCHAR | java.lang.String | text |
CharacterType | CHAR | char, java.lang.Character | char, java.lang.Character |
BooleanType | BIT | boolean, java.lang.Boolean | boolean, java.lang.Boolean |
NumericBooleanType | INTEGER, 0 is false, 1 is true | boolean, java.lang.Boolean | numeric_boolean |
YesNoType | CHAR, ‘N’/’n’ is false, ‘Y’/’y’ is true. The uppercase value is written to the database. | boolean, java.lang.Boolean | yes_no |
TrueFalseType | CHAR, ‘F’/’f’ is false, ‘T’/’t’ is true. The uppercase value is written to the database. | boolean, java.lang.Boolean | true_false |
ByteType | TINYINT | byte, java.lang.Byte | byte, java.lang.Byte |
ShortType | SMALLINT | short, java.lang.Short | short, java.lang.Short |
IntegerTypes | INTEGER | int, java.lang.Integer | int, java.lang.Integer |
LongType | BIGINT | long, java.lang.Long | long, java.lang.Long |
FloatType | FLOAT | float, java.lang.Float | float, java.lang.Float |
DoubleType | DOUBLE | double, java.lang.Double | double, java.lang.Double |
BigIntegerType | NUMERIC | java.math.BigInteger | big_integer, java.math.BigInteger |
BigDecimalType | NUMERIC | java.math.BigDecimal | big_decimal, java.math.bigDecimal |
TimestampType | TIMESTAMP | java.sql.Timestamp | timestamp, java.sql.Timestamp |
TimeType | TIME | java.sql.Time | time, java.sql.Time |
DateType | DATE | java.sql.Date | date, java.sql.Date |
CalendarType | TIMESTAMP | java.util.Calendar | calendar, java.util.Calendar |
CalendarDateType | DATE | java.util.Calendar | calendar_date |
CurrencyType | java.util.Currency | VARCHAR | currency, java.util.Currency |
LocaleType | VARCHAR | java.util.Locale | locale, java.utility.locale |
TimeZoneType | VARCHAR, using the TimeZone ID | java.util.TimeZone | timezone, java.util.TimeZone |
UrlType | VARCHAR | java.net.URL | url, java.net.URL |
ClassType | VARCHAR (class FQN) | java.lang.Class | class, java.lang.Class |
Таблица, конечно, большая, но очень полезная. Например, из нее понятно, что тип Boolean можно сохранить в базу как минимум шестью разными способами. Тебе столько не нужно? А кто сказал, что способ сохранения выбираешь ты?
В SQL нет типа Boolean и его часто хранят так:
Поэтому очень хорошо, когда Hibernate понимает все эти заморочки. Или, например, давай возьмем хранение массивов данных в базе. Есть куча разных вариантов и Hibernate умеет работать с ними со всеми:
Hibernate type (org.hibernate.type package) | JDBC type | Java type | BasicTypeRegistr |
---|---|---|---|
BlobType | BLOB | java.sql.Blob | blog, java.sql.Blob |
ClobType | CLOB | java.sql.Clob | clob, java.sql.Clob |
BinaryType | VARBINARY | byte[] | binary, byte[] |
MaterializedBlobType | BLOB | byte[] | materized_blob |
ImageType | LONGVARBINARY | byte[] | image |
WrapperBinaryType | VARBINARY | java.lang.Byte[] | wrapper-binary, Byte[], java.lang.Byte[] |
CharArrayType | VARCHAR | char[] | characters, char[] |
CharacterArrayType | VARCHAR | java.lang.Character[] | wrapper-characters, Character[], java.lang.Character[] |
UUIDBinaryType | BINARY | java.util.UUID | uuid-binary, java.util.UUID |
UUIDCharType | CHAR, can also read VARCHAR | java.util.UUID | uuid-char |
PostgresUUIDType | PostgreSQL UUID, through Types#OTHER, which complies to the PostgreSQL JDBC driver definition | java.util.UUID | pg-uuid |
А после выпуска JDK 8 в Hibernate добавилось еще несколько типов, связанных со временем. И все с целью упростить тебе жизнь. Тебе больше не нужно думать, поддерживаются ли все эти новомодные типы. Создатели Hibernate уже добавили их поддержку для тебя:
Hibernate type (org.hibernate.type package) | JDBC type | Java type | BasicTypeRegistr |
---|---|---|---|
DurationType | BIGINT | java.time.Duration | Duration, java.time.Duration |
InstantType | TIMESTAMP | java.time.Instant | Instant, java.time.Instant |
LocalDateTimeType | TIMESTAMP | java.time.LocalDateTime | LocalDateTime, java.time.LocalDateTime |
LocalDateType | DATE | java.time.LocalDate | LocalDate, java.time.LocalDate |
LocalTimeType | TIME | java.time.LocalTime | LocalTime, java.time.LocalTime |
OffsetDateTimeType | TIMESTAMP | java.time.OffsetDateTime | OffsetDateTime, java.time.OffsetDateTime |
OffsetTimeType | TIME | java.time.OffsetTime | OffsetTime, java.time.OffsetTime |
OffsetTimeType | TIMESTAMP | java.time.ZonedDateTime | ZonedDateTime, java.time.ZonedDateTime |
The JDBC Boolean Compatibility List
Interestingly, boolean types have been introduced only late in the SQL standard, namely in SQL:1999. Even today, not all databases natively support BOOLEAN or BIT types. Most importantly, we can still wait for them in Oracle for a while. Here’s “Ask Tom”‘s point of view from 2002 on the subject: https://asktom.oracle.com/pls/apex/f?p=100:11:0. P11_QUESTION_ID:6263249199595
People have worked around this limitation by using numeric or string literals instead. For instance 1 / 0 , Y / N , T / F or the SQL standard ‘true’ / ‘false’ .
Booleans in JDBC
From a JDBC API perspective, boolean values can be set as bind values through PreparedStatement.setBoolean() or fetched from result sets through ResultSet.getBoolean() , and similar methods. If your database supports booleans, the Java boolean type nicely maps to SQL BOOLEAN – even if Java’s Boolean wrapper type would have been a better fit to respect NULLs . But if you’re storing boolean values in INTEGER , CHAR(1) or VARCHAR(1) columns, things look differently in various databases. Consider the following example:
CREATE TABLE booleans ( val char(1) );
try < DSL.using(configuration) .execute( "insert into boolean (val) values (?)", true); >catch (Exception e) < e.printStackTrace(); >DSL.using(configuration) .fetch("select * from booleans");
- Firebird (inserts ‘Y’ or ‘N’)
- HSQLDB (inserts ’1′ or ’0′)
- IBM DB2 (inserts ’1′ or ’0′)
- MariaDB (inserts ’1′ or ’0′)
- Microsoft Access (inserts ’1′ or ’0′)
- MySQL (inserts ’1′ or ’0′)
- Oracle (inserts ’1′ or ’0′)
- SQL Server (inserts ’1′ or ’0′)
- Sybase (inserts ’1′ or ’0′)
Booleans in the SQL standard
It is worth mentioning, that the SQL standard specifies how to deal with boolean to string conversion in the specification of the CAST() function:
6.13 [. ] 10) If TD is fixed-length character string, then let LTD be the length in characters of TD. [. ] e) If SD is boolean, then Case: i) If SV is True and LTD is not less than 4, then TV is 'TRUE' extended on the right by LTD–4 s. ii) If SV is False and LTD is not less than 5, then TV is 'FALSE' extended on the right by LTD–5 s. iii) Otherwise, an exception condition is raised: data exception — invalid character value for cast.
So, most Open Source databases show what could be interpreted as the “correct” behaviour, even if from a historic perspective, 1/0 should be accepted behaviours. Beware of this limitation when using an Open Source test database!
For more information about this and the H2 database, please refer to this thread on the H2 user group.
The JDBC Boolean Compatibility List
Interestingly, boolean types have been introduced only late in the SQL standard, namely in SQL:1999. Even today, not all databases natively support BOOLEAN or BIT types. Most importantly, we can still wait for them in Oracle for a while. Here’s “Ask Tom”‘s point of view from 2002 on the subject: https://asktom.oracle.com/pls/apex/f?p=100:11:0. P11_QUESTION_ID:6263249199595
People have worked around this limitation by using numeric or string literals instead. For instance 1 / 0 , Y / N , T / F or the SQL standard ‘true’ / ‘false’ .
Booleans in JDBC
From a JDBC API perspective, boolean values can be set as bind values through PreparedStatement.setBoolean() or fetched from result sets through ResultSet.getBoolean() , and similar methods. If your database supports booleans, the Java boolean type nicely maps to SQL BOOLEAN – even if Java’s Boolean wrapper type would have been a better fit to respect NULLs . But if you’re storing boolean values in INTEGER , CHAR(1) or VARCHAR(1) columns, things look differently in various databases. Consider the following example:
CREATE TABLE booleans ( val char(1) );
try < DSL.using(configuration) .execute( "insert into boolean (val) values (?)", true); >catch (Exception e) < e.printStackTrace(); >DSL.using(configuration) .fetch("select * from booleans");
- Firebird (inserts ‘Y’ or ‘N’)
- HSQLDB (inserts ’1′ or ’0′)
- IBM DB2 (inserts ’1′ or ’0′)
- MariaDB (inserts ’1′ or ’0′)
- Microsoft Access (inserts ’1′ or ’0′)
- MySQL (inserts ’1′ or ’0′)
- Oracle (inserts ’1′ or ’0′)
- SQL Server (inserts ’1′ or ’0′)
- Sybase (inserts ’1′ or ’0′)
Booleans in the SQL standard
It is worth mentioning, that the SQL standard specifies how to deal with boolean to string conversion in the specification of the CAST() function:
6.13 [. ] 10) If TD is fixed-length character string, then let LTD be the length in characters of TD. [. ] e) If SD is boolean, then Case: i) If SV is True and LTD is not less than 4, then TV is 'TRUE' extended on the right by LTD–4 s. ii) If SV is False and LTD is not less than 5, then TV is 'FALSE' extended on the right by LTD–5 s. iii) Otherwise, an exception condition is raised: data exception — invalid character value for cast.
So, most Open Source databases show what could be interpreted as the “correct” behaviour, even if from a historic perspective, 1/0 should be accepted behaviours. Beware of this limitation when using an Open Source test database!
For more information about this and the H2 database, please refer to this thread on the H2 user group.