Мапим 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;
}

Мапим 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;

Ну, а если базу данных проектируешь не ты, то смотри таблицу выше и думай, как правильно замапить нужные тебе типы.

Вычисляемые поля

Иногда количество полей в 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;

@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.

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