1. Мапимо enum

Як мапити примітивні типи даних ми вже розібралися: використовуємо анотацію @Column та анотацію @Type. Але не всі випадки можна покрити цими анотаціями. І найчастіший випадок — це enum.

Java-об'єкти типу enum можуть зберігатися в базі у двох варіантах:

  • у вигляді числа
  • у вигляді рядка

Давай напишемо невеликий приклад, де користувач матиме улюблений колір, який встановлюється за допомогою enum.


enum Color {
   RED,
   ORANGE,
   YELLOW,
   GREEN,
   BLUE,
   VIOLET
}

І додамо поле з кольором до класу User:


@Entity
@Table(name="user")
class User
{
   @Column(name="id")
   public Integer id;
 
   @Column(name="favorite_color")
   public Color favoriteColor;
 
   @Column(name="created_date")
   public Date createdDate;
}

Якщо ми хочемо, щоб Hibernate зберігав тип Color у базі як числа, то потрібно полю favoriteColor додати анотацію:


@Enumerated(EnumType.ORDINAL)

Якщо ми хочемо, щоб значення зберігалися як рядки, потрібно додати анотацію:


@Enumerated(EnumType.STRING)

Приклад:


@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;
}

2. Мапимо Boolean

Другий корисний сценарій — це мапінг типу Boolean. Так вже історично склалося, що SQL не має свого типу даних для Boolean, і замість нього там використовують усе що завгодно.

Три найпоширеніші варіанти:

  • 1 або 0
  • 'F' або 'T'
  • 'Y' або 'N'

Загалом, якщо ти проєктуватимеш свою базу, краще одразу пиши тип BIT. Ну а повний мапінг для нього виглядатиме так:


@Column(name = "is_correct", columnDefinition = "BIT")
@Type(type = "org.hibernate.type.NumericBooleanType")
    private Boolean isCorrect;

Ну а якщо базу даних проєктуєш не ти, то дивись таблицю вище та думай, як правильно замапити потрібні тобі типи.

3. Обчислювані поля

Іноді кількість полів в Entity-класі та кількість колонок у таблиці не збігаються. Це може статися через кілька причин.

Найпоширеніша — це коли в нашому Entity-класі є якесь поле, яке ми не хочемо зберігати в базі даних. З цим все зрозуміло — просто додай такому полю анотацію @Transient, і Hibernate проігнорує його під час роботи з базою даних.

Приклад:


@Entity(name = "Square")
public class Square {
           @Id
           public Long id;
 
           public Integer width;
 
           public Integer height;
 
           @Transient
           public Integer total;
}

Це хороший варіант, але під час читання об'єкта з бази поле total буде null. А нам хотілося б, щоб він містив добуток width*height. Таке теж можна зробити в Hibernate. Для цього є спеціальна анотація @Formula.


@Entity(name = "Square")
public class Square {
           @Id
           public Long id;
 
           public Integer width;
 
           public Integer height;
 
           @Formula(value = " width * height ")
          public Integer total;
}

Таке поле не буде зберігатися в базі даних, а під час читання об'єкта з бази до нього буде записуватися значення, обчислене за формулою.

SQL-запит буде виглядати приблизно так:

SELECT id, width, height, (width* height) AS total FROM Square;

4. @Embedded

Ще одна корисна анотація — це @Embedded. Вона дозволяє розглядати поля дочірнього об'єкта як поля самого Entity-класу.

Припустимо, у тебе є клас User, і ти вирішив додати до нього адресу:


@Entity
@Table(name="user")
class User
{
   @Column(name="id")
    public Integer id;
 
   @Column(name="user_address_country")
   public String country;
   @Column(name="user_address_city")
   public String city;
   @Column(name="user_address_street")
   public String street;
   @Column(name="user_address_home")
   public String home;
 
   @Column(name="created_date")
    public Date createdDate;
}

Все ніби добре, але з погляду Java було б логічно винести адресу до окремого класу. Адреса — все ж таки окрема сутність. Але як це зробити, якщо у базі вся ця інформація зберігається саме в таблиці user?

Нам допоможе анотація @Embedded. Спочатку ми створимо клас UserAddress і винесемо всю інформацію про адресу користувача:


@Embeddable
class UserAddress
{
   @Column(name="user_address_country")
   public String country;
   @Column(name="user_address_city")
   public String city;
   @Column(name="user_address_street")
   public String street;
   @Column(name="user_address_home")
   public String home;
}

А потім використовуємо поле цього класу в нашому класі User:


@Entity
@Table(name="user")
class User
{
   @Column(name="id")
   public Integer id;
 
   @Embedded
   public UserAddress address;
 
   @Column(name="created_date")
   public Date createdDate;
}

Завдяки анотації @Embedded під час збереження об'єкта Hibernate зрозуміє, що поля класу UserAddress потрібно обробляти як поля самого класу User.

Важливо! Якщо ти вирішиш додати до свого класу User два поля UserAddress, то використовувати @Embedded вже не вийде: у тебе буде дублікат полів і потрібно буде якось їх розділити. Робиться це за допомогою перевизначення анотацій: за допомогою анотації @AttributeOverrides.

Я хочу, щоб ти це знав, але детально тут ми все це розбирати не будемо. Думаю, тобі цього вистачить, щоб голову поламати. Якщо дуже цікаво дізнатися більше, можу залишити посилання на офіційну документацію.