Перейти к основному содержимому

Сохранение карт с помощью Hibernate

· 5 мин. чтения

1. Введение

В Hibernate мы можем представить отношения «один ко многим » в наших Java-бинах, если одним из наших полей будет список .

В этом кратком руководстве мы рассмотрим различные способы сделать это с помощью карты .

2. Карты отличаются от списков

Использование карты для представления отношения «один ко многим» отличается от списка , поскольку у нас есть ключ.

Этот ключ превращает наше отношение сущностей в троичную ассоциацию, где каждый ключ относится к простому значению, встраиваемому объекту или сущности. Из-за этого, чтобы использовать Map , нам всегда нужна таблица соединения для хранения внешнего ключа, который ссылается на родительский объект — ключ и значение.

Но эта таблица соединений будет немного отличаться от других таблиц соединений тем, что первичный ключ не обязательно будет внешним ключом к родителю и цели. Вместо этого у нас будет первичный ключ, состоящий из внешнего ключа родителя и столбца, который является ключом к нашей Карте.

Пара ключ-значение в Map может быть двух типов: Value Type и Entity Type . В следующих разделах мы рассмотрим способы представления этих ассоциаций в Hibernate.

3. Использование @MapKeyColumn

Допустим, у нас есть сущность Order , и мы хотим отслеживать название и цену всех товаров в заказе. Итак, мы хотим ввести Map<String, Double> в Order , который будет сопоставлять название предмета с его ценой:

@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue
@Column(name = "id")
private int id;

@ElementCollection
@CollectionTable(name = "order_item_mapping",
joinColumns = {@JoinColumn(name = "order_id", referencedColumnName = "id")})
@MapKeyColumn(name = "item_name")
@Column(name = "price")
private Map<String, Double> itemPriceMap;

// standard getters and setters
}

Нам нужно указать Hibernate, где взять ключ и значение. В качестве ключа мы использовали @MapKey Column , указывая, что ключ карты — это столбец item_name нашей таблицы соединения, order_item_mapping . Точно так же @Column указывает, что значение карты соответствует столбцу цены в таблице соединений.

Кроме того, объект itemPriceMap является картой типа значения, поэтому мы должны использовать аннотацию @ElementCollection .

В дополнение к объектам базового типа значения объекты @ Embeddable также могут использоваться в качестве значений Map аналогичным образом.

4. Использование @MapKey

Как мы все знаем, требования со временем меняются — так что теперь, скажем, нам нужно сохранить еще несколько атрибутов Item вместе с itemName и itemPrice :

@Entity
@Table(name = "item")
public class Item {
@Id
@GeneratedValue
@Column(name = "id")
private int id;

@Column(name = "name")
private String itemName;

@Column(name = "price")
private double itemPrice;

@Column(name = "item_type")
@Enumerated(EnumType.STRING)
private ItemType itemType;

@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_on")
private Date createdOn;

// standard getters and setters
}

Соответственно, изменим Map<String, Double> на Map<String, Item> в классе сущностей Order :

@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue
@Column(name = "id")
private int id;

@OneToMany(cascade = CascadeType.ALL)
@JoinTable(name = "order_item_mapping",
joinColumns = {@JoinColumn(name = "order_id", referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "item_id", referencedColumnName = "id")})
@MapKey(name = "itemName")
private Map<String, Item> itemMap;

}

Обратите внимание, что на этот раз мы будем использовать аннотацию @MapKey , чтобы Hibernate использовал Item# itemName в качестве столбца ключа сопоставления вместо добавления дополнительного столбца в таблицу соединений. Таким образом, в этом случае таблица соединения order_item_mapping не имеет ключевого столбца — вместо этого он ссылается на имя элемента I. ``

Это отличается от @MapKeyColumn. Когда мы используем @MapKeyColumn, ключ карты находится в таблице соединений . Это причина, по которой мы не можем определить наше сопоставление сущностей, используя обе аннотации вместе.

Кроме того, itemMap является картой типа сущности, поэтому мы должны аннотировать отношения, используя @OneToMany или @ManyToMany .

5. Использование @MapKeyEnumerated и @MapKeyTemporal

Всякий раз, когда мы указываем перечисление в качестве ключа карты , мы используем @MapKeyEnumerated . Точно так же для временных значений используется @MapKeyTemporal . Поведение очень похоже на стандартные аннотации @Enumerated и @Temporal соответственно.

По умолчанию они аналогичны @MapKeyColumn тем, что ключевой столбец будет создан в таблице соединений. Если мы хотим повторно использовать значение, уже сохраненное в сохраняемом объекте, мы должны дополнительно пометить поле с помощью @MapKey .

6. Использование @MapKeyJoinColumn

Далее, допустим, нам также нужно отслеживать продавца каждого товара. Один из способов сделать это — добавить сущность Seller и связать ее с нашей сущностью Item :

@Entity
@Table(name = "seller")
public class Seller {

@Id
@GeneratedValue
@Column(name = "id")
private int id;

@Column(name = "name")
private String sellerName;

// standard getters and setters

}
@Entity
@Table(name = "item")
public class Item {
@Id
@GeneratedValue
@Column(name = "id")
private int id;

@Column(name = "name")
private String itemName;

@Column(name = "price")
private double itemPrice;

@Column(name = "item_type")
@Enumerated(EnumType.STRING)
private ItemType itemType;

@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_on")
private Date createdOn;

@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "seller_id")
private Seller seller;

// standard getters and setters
}

В этом случае предположим, что наш прецедент состоит в том, чтобы сгруппировать все элементы Order по продавцу . Следовательно, давайте изменим Map<String, Item> на Map<Seller, Item> :

@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue
@Column(name = "id")
private int id;

@OneToMany(cascade = CascadeType.ALL)
@JoinTable(name = "order_item_mapping",
joinColumns = {@JoinColumn(name = "order_id", referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "item_id", referencedColumnName = "id")})
@MapKeyJoinColumn(name = "seller_id")
private Map<Seller, Item> sellerItemMap;

// standard getters and setters

}

Для этого нам нужно добавить @MapKeyJoinColumn , поскольку эта аннотация позволяет Hibernate сохранять столбец продавца_id (ключ карты) в таблице соединений order_item_mapping вместе со столбцом item_id . Итак, во время чтения данных из базы данных мы можем легко выполнить операцию GROUP BY .

7. Заключение

В этой статье мы узнали о нескольких способах сохранения Map в Hibernate в зависимости от требуемого сопоставления.

Как всегда, исходный код этого туториала можно найти на Github .