- Полезные сценарии маппинга данных
- Мапим Boolean
- Вычисляемые поля
- @Embedded
- Enum Mappings with Hibernate – The Complete Guide
- JPA & Hibernate Standard Enum Mappings
- Customized Mapping to a Supported Basic Type
- Use the AttributeConverter
- Customized Mapping to a Database-Specific Enum Type
- Create a DB-Specific Enum Type
- Use Your Custom Type
- Conclusion
Полезные сценарии маппинга данных
Как мапить примитивные типы данных мы уже разобрались: используем аннотации @Column и аннотацию @Type . Но не все случаи можно покрыть этими аннотациями. И самый частый случай – это enum.
Java-объекты типа enum могут храниться в базе в двух вариантах:
Давай напишем небольшой пример, где у пользователя будет любимый цвет, который задается с помощью enum.
И добавим поле с цветом в класс User :
@Entity @Table(name="user") class User
Если мы хотим, чтобы Hibernate сохранял тип Color в базу как числа, то нужно полю favoriteColor добавить аннотацию:
@Enumerated(EnumType.ORDINAL)
Если мы хотим, чтобы значения хранились как строки, то нужно добавить аннотацию:
@Entity @Table(name="user") class User < @Column(name="id") public Integer id; @Enumerated(EnumType.ORDINAL) //значение будет сохранено в базу как число @Column(name="favorite_color") public Color favoriteColor; @Column(name="created_date") public Date createdDate; >
Мапим Boolean
Второй полезный сценарий – это маппинг типа Boolean. Так уж исторически сложилось, что в SQL нет своего типа данных для Boolean и вместо него там используют все что угодно.
Три самых распространенных варианта:
В общем, если ты будешь проектировать свою базу, то лучше сразу пиши тип BIT. Ну а полный маппинг для него будет выглядеть так:
@Column(name = "is_correct", columnDefinition = "BIT") @Type(type = "org.hibernate.type.NumericBooleanType") private Boolean isCorrect;
Ну, а если базу данных проектируешь не ты, то смотри таблицу выше и думай, как правильно замапить нужные тебе типы.
Вычисляемые поля
Иногда количество полей в Entity-классе и количество колонок в таблице не совпадают. У этого может быть несколько причин.
Самая распространенная – это когда в нашем Entity-классе есть какое-то поле, которое мы не хотим сохранять в базу данных. С этим все понятно – просто добавь такому полю аннотацию @Transient и Hibernate проигнорирует его при работе с базой данных.
@Entity(name = "Square") public class Square
Это хороший вариант, но при чтении объекта из базы поле total будет null. А нам бы хотелось, чтобы оно содержало произведение width*height. Такое тоже можно сделать в Hibernate. Для этого есть специальная аннотация @Formula .
@Entity(name = "Square") public class Square
Такое поле не будет сохраняться в базу данных, а при чтении объекта из базы в него будет записываться значение вычисленное по формуле.
SQL-запрос будет выглядеть примерно так:
SELECT id, width, height, (width* height) AS total FROM Square;
@Embedded
Еще одна полезная аннотация – это @Embedded . Она позволяет рассматривать поля дочернего объекта как поля самого Entity-класса.
Допустим, у тебя есть класс User и ты решил добавить в него адрес:
@Entity @Table(name="user") class User
Все вроде бы хорошо, но с точки зрения Java было бы логично вынести адрес в отдельный класс. Адрес все-таки отдельная сущность. Но как это сделать, если в базе вся эта информация хранится именно в таблице user?
Нам поможет аннотация @Embedded . Сначала мы создадим класс UserAddress и вынесем в него всю информацию по адресу пользователя:
@Embeddable class UserAddress
А затем используем поле этого класса в нашем классе User :
@Entity @Table(name="user") class User
Благодаря аннотации @Embedded во время сохранения объекта Hibernate поймет, что поля класса UserAddress нужно обрабатывать как поля самого класса User .
Важно! Если ты решишь добавить в свой класс User два поля UserAddress , то использовать @Embedded уже не получится: у тебя будет дубликат полей и тебе нужно будет как-то их разделить. Делается это с помощью переопределения аннотаций: с помощью аннотации @AttributeOverrides .
Я хочу, чтоб ты это знал, но детально тут мы это все разбирать не будем. Думаю, тебе и этого хватит, чтобы голову поломать. Для любопытных могу оставить ссылку на официальную документацию.
Enum Mappings with Hibernate – The Complete Guide
The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, 2 monthly Q&A calls, monthly coding challenges, a community of like-minded developers, and regular expert sessions.
Most developers use enums in their domain models. You can easily map them with Hibernate. They provide a strongly typed way to model an attribute that has one out of a list of defined values. The format of a book is a typical example of such an enum. Supported values could be hardcover, paperback, and ebook.
With JPA and Hibernate, you can map enums in different ways. You can:
- use the standard mappings to a number or a String,
- create a customized mapping to any basic type supported by Hibernate,
- define a custom mapping to a database-specific type, like PostgreSQLs enum type.
In this article, I will show you how to use all 3 of these options to map the following Rating enum. It’s used in the Review entity to represent the rating of a book.
JPA & Hibernate Standard Enum Mappings
Enums are a first-class citizen of the Java language and used in most domain models. So, it’s no surprise that JPA and Hibernate provide a standard mapping for them.
You can choose between 2 mappings:
If you instead want to persist the String representation of your enum value, you need to annotate your entity attribute with @Enumerated(EnumType.STRING).
@Entity public class Review
When you take a look at the database, you can see that Hibernate now persisted the name of the enum value.
Customized Mapping to a Supported Basic Type
The 2 previously shown standard mappings are good enough for most domain models. But they can make a refactoring of your persistence layer harder than it should be. They are also not flexible enough to handle the mapping used by most legacy data models.
Use the AttributeConverter
But before you try to use this mapping, you need to put the right annotations on your entity attribute.
You can’t use an AttributeConverter on an entity attribute that’s annotated with @Enumerated. So, you need to remove that annotation from your mapping if you want to use the custom mapping.
If you didn’t annotate your converter with @Converter(autoApply=true), you also need to annotate the entity attribute with @Convert(converter = RatingAttributeConverter.class). This tells Hibernate to use the referenced converter when it reads or writes this entity attribute.
@Entity public class Review
Hibernate applies the converter transparently whenever you use the Review entity and its rating attribute in your business code, a JPQL or a CriteriaQuery. So, you can use the rating attribute in the same way as any other entity attribute.
Customized Mapping to a Database-Specific Enum Type
Some databases, like PostgreSQL, offer custom data types to store enumerations. These data types are similar to the enum type that we know in Java. They define a set of valid values that can be stored in the database column.
CREATE TYPE rating_enum AS ENUM ( 'ONE', 'TWO', 'THREE', 'FOUR', 'FIVE' )
In the following examples, I use PostgreSQL’s enum type. But you can use the same approach to support similar types supported by other DBMS.
Create a DB-Specific Enum Type
Unfortunately, you can’t use Hibernate’s default mapping to map your Java enum to a PostgreSQL enum. As explained earlier, Hibernate maps the enum values to an int or a String. But PostgreSQL expects you to set the value as an Object.
If you want to map your enum to PostgreSQL’s enum type, you need to implement a custom mapping. But don’t worry, if you extend Hibernate’s EnumType, you just need to override 1 method to set the value as an object. That only requires 4 lines of code.
public class EnumTypePostgreSql extends EnumType < @Override public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException < if(value == null) < st.setNull( index, Types.OTHER ); >else < st.setObject( index, value.toString(), Types.OTHER ); >> >
And to make it even better, you can use your custom type to map any Java enum to a PostgreSQL enum. So, you can use this type for all enums in your project, and you can even add it to one of your internal libraries.
Use Your Custom Type
You can use the custom type in your entity mappings in 2 ways.
You can reference the class of your enum type in a @Type annotation on your entity attribute. This is a good approach if you only use the type on one entity attribute.
@Entity public class Review
When you use this mapping, Hibernate uses the EnumTypePostgreSql to map the Rating value to a PostgreSQL-specific enum type.
If you use the type to map multiple entity attributes, you should register your type using a @TypeDef annotation. You can either add the annotation to one of your entities or put it into a package-info.java file.
@org.hibernate.annotations.TypeDef(name = "enum_postgressql", typeClass = EnumTypePostgreSql.class) package org.thoughts.on.java.model;
After you’ve done that, you can reference the type by its logical name in your entity mapping.
@Entity public class Review
Conclusion
With Hibernate, you can map enums in 3 ways:
- Hibernate’s standard mappings provide a simple way to store enum values in your database. You can choose if you want to persist the String representation of your enum value or if you prefer its ordinal value. The String representation is more robust, while the ordinal value is more efficient.
- If you need to map a legacy database or if you prefer a mapping that you can easily refactor, you should implement your own mapping using anAttributeConverter.
- JPA and Hibernate don’t support any database-specific enum types. If you want to use them in your table model, you need to implement your own enum type by extending Hibernate’s EnumType.