«1. Введение
В Hibernate мы можем представить отношения «один ко многим» в наших Java-бинах, задав одно из наших полей в виде списка.
В этом кратком руководстве мы рассмотрим различные способы сделать это с помощью карты.
2. Карты отличаются от списков
Использование карты для представления отношения «один ко многим» отличается от списка, поскольку у нас есть ключ.
Этот ключ превращает наше отношение сущностей в тернарную ассоциацию, где каждый ключ относится к простому значению, встраиваемому объекту или сущности. Из-за этого, чтобы использовать карту, нам всегда нужна таблица соединения для хранения внешнего ключа, который ссылается на родительский объект — ключ и значение.
Но эта таблица соединений будет немного отличаться от других таблиц соединений тем, что первичный ключ не обязательно будет внешним ключом к родителю и цели. Вместо этого у нас будет первичный ключ, состоящий из внешнего ключа родителя и столбца, который является ключом к нашей Карте.
Пара ключ-значение на карте может быть двух типов: тип значения и тип объекта. В следующих разделах мы рассмотрим способы представления этих ассоциаций в Hibernate.
3. Использование @MapKeyColumn
Допустим, у нас есть сущность Order, и мы хотим отслеживать название и цену всех товаров в заказе. Итак, мы хотим ввести Map\u003cString, Double\u003e в 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, где взять ключ и значение. В качестве ключа мы использовали @MapKeyColumn, указывая, что ключ карты — это столбец item_name нашей таблицы соединения, order_item_mapping. Точно так же @Column указывает, что значение карты соответствует столбцу цены в таблице соединений.
Кроме того, объект itemPriceMap является картой типа значения, поэтому мы должны использовать аннотацию @ElementCollection.
В дополнение к объектам базового типа значения, объекты @Embeddable также могут использоваться в качестве значений карты аналогичным образом.
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\u003cString, Double\u003e на Map\u003cString, Item\u003e в классе сущности 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 не имеет ключевого столбца — вместо этого он ссылается на имя элемента.
Это отличается от @MapKeyColumn. Когда мы используем @MapKeyColumn, ключ карты находится в таблице соединений. Это причина, по которой мы не можем определить наше сопоставление сущностей, используя обе аннотации вместе.
Кроме того, itemMap является картой типа объекта, поэтому мы должны аннотировать отношения, используя @OneToMany или @ManyToMany.
5. Использование @MapKeyEnumerated и @MapKeyTemporal
Всякий раз, когда мы указываем перечисление в качестве ключа карты, мы используем @MapKeyEnumerated. Точно так же для временных значений используется @MapKeyTemporal. Поведение очень похоже на стандартные аннотации @Enumerated и @Temporal соответственно.
По умолчанию они аналогичны @MapKeyColumn тем, что ключевой столбец будет создан в таблице соединений. Если мы хотим повторно использовать значение, уже сохраненное в сохраняемом объекте, мы должны дополнительно пометить поле с помощью @MapKey.
6. Использование @MapKeyJoinColumn
Далее, допустим, нам также нужно отслеживать продавца каждого товара. Один из способов сделать это — добавить сущность Продавца и связать ее с сущностью Товара: . Следовательно, давайте изменим Map\u003cString, Item\u003e на Map\u003cSeller, Item\u003e:
@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
}
Нам нужно добавить @MapKeyJoinColumn, чтобы добиться этого, так как эта аннотация позволяет Hibernate сохранять столбецeller_id (ключ карты) в присоединиться к таблице order_item_mapping вместе со столбцом item_id. Итак, во время чтения данных из базы данных мы можем легко выполнить операцию GROUP BY.
@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
}
«7. Заключение
В этой статье мы узнали о нескольких способах сохранения Map в Hibernate в зависимости от требуемого сопоставления.
Как всегда, исходный код этого руководства можно найти на Github.
«