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 .