JavaRush /Курсы /Модуль 4. Работа с БД /Полезные сценарии маппинга данных

Полезные сценарии маппинга данных

Модуль 4. Работа с БД
12 уровень , 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;
}

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

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

1
Задача
Модуль 4. Работа с БД, 12 уровень, 1 лекция
Недоступна
Создаём Entity из класса
Если ранее не подключал зависимости, то подключи их. Для этого Alt + Ctrl + Shift + S (в Идее), вкладка Libraries. Зависимости можно скачать здесь: https://javarush.com/downloads/ide/javarush/hibernate.zip Архив распакуй, и каждую зависимость добавь к модулю. Эта часть задания не проверяется, но если ее не выполнить, ты не сможешь локально выполнять код.
1
Задача
Модуль 4. Работа с БД, 12 уровень, 1 лекция
Недоступна
Entity с вычислением
Если ранее не подключал зависимости, то подключи их. Для этого используй Alt + Ctrl + Shift + S (в Идее), вкладка Libraries. Зависимости можно скачать здесь: https://javarush.com/downloads/ide/javarush/hibernate.zip Архив распакуй, и каждую зависимость добавь к модулю. Эта часть задания не проверяется, но если ее не выполнить, ты не сможешь локально выполнять код.
Комментарии (7)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Alibek Уровень 1 Expert
29 октября 2024
оказывается нельзя ставить column в total
Alibek Уровень 1 Expert
29 октября 2024
@Formula(value = " width * height ") public Integer total; не работает постоянно вытаскивает null
Михаил Шапошников Уровень 1 Expert
13 апреля 2024
На сколько я знаю если делать по правильному , для адреса нужно было делать отдельную таблицу и энтити и связывать с юзером.
Gans Electro Уровень 4
27 июля 2024
Зависит от бизнеса. Во первых, это делает запрос слишком сложным и дорогостоящим. В распределенных системах таблицы могут находится в разных серверах и локациях (запрос будет дольше). Также это делает разработку сложнее из-за десятка Join-ов. Поэтому, в ХД (хранилище данных) делают денормализованные таблицы ради оптимизации. Во вторых, адреса могут не иметь смысла без User-a. В таком случае на 1 000 000 User-ов будет 1 000 000 адресов. Чтобы сделать нормализацию различные части таблицы должны иметь смысл по отдельности. Например, поля country или city могут иметь смысл по расположению точек продаж или в разрезе город-доход, страна-количество заказов и иметь смысл в отрыве от User
Дмитрий Уровень 37
16 марта 2024
касательно Embedded. Больше преимуществ в использовании разве нет? Только для декомпозиции на уровне джава кода?
Igor Уровень 108 Expert
18 октября 2023
Аннотация @Embedded работает в паре с @Embeddable
Александр Уровень 111 Expert
19 января 2023
Сначала показывают как правильно указывать название полей, каждое новое имя через нижнее подчеркивание. А потом просят делать неправильно и указывать название через camelCase