1. Introduction
1.绪论
In Hibernate, we can represent one-to-many relationships in our Java beans by having one of our fields be a List.
在 Hibernate 中,我们可以通过让其中一个字段成为 List,在我们的 Java Bean 中表示 one-to-many 关系。
In this quick tutorial, we’ll explore various ways of doing this with a Map instead.
在这个快速教程中,我们将探讨用Map代替的各种方法。
2. Maps Are Different from Lists
2.地图与列表的区别
Using a Map to represent a one-to-many relationship is different from a List because we have a key.
使用Map来表示一对多的关系与List不同,因为我们有一个键。
This key turns our entity relationship into a ternary association, where each key refers to a simple value or an embeddable object or an entity. Because of this, to use a Map, we’ll always need a join table to store the foreign key that references the parent entity – the key, and the value.
这个键将我们的实体关系变成了外键关联,其中每个键指的是一个简单的值或一个可嵌入的对象或一个实体。正因为如此,为了使用Map,我们总是需要一个连接表来存储引用父实体的外键–键,以及值。
But this join table will be a bit different from other join tables in that the primary key won’t necessarily be foreign keys to the parent and the target. Instead, we’ll have the primary key be a composite of a foreign key to the parent and a column that is the key to our Map.
但是这个连接表与其他连接表有些不同,因为主键不一定是父键和目标的外键。相反,我们将让主键成为父键的外键和作为我们Map.的键的列的组合。
The key-value pair in the Map may be of two types: Value Type and Entity Type. In the following sections, we’ll look at the ways to represent these associations in Hibernate.
Map中的键-值对可以有两种类型。值类型和实体类型。在下面的章节中,我们将了解在Hibernate中表示这些关联的方法。
3. Using @MapKeyColumn
3.使用@MapKeyColumn
Let’s say we have an Order entity and we want to keep track of name and price of all the items in an order. So, we want to introduce a Map<String, Double> to Order which will map the item’s name to its price:
假设我们有一个Order实体,我们想跟踪一个订单中所有物品的名称和价格。所以,我们想为Order引入一个Map<String, Double> ,它将把物品的名称映射到它的价格:。
@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
}
We need to indicate to Hibernate where to get the key and the value. For the key, we’ve used @MapKeyColumn, indicating that the Map‘s key is the item_name column of our join table, order_item_mapping. Similarly, @Column specifies that the Map’s value corresponds to the price column of the join table.
我们需要向 Hibernate 指出从哪里获取键和值。对于键,我们使用了 @MapKeyColumn,表明Map的键是我们的连接表item_name列,order_item_mapping。同样,@Column指定Map的值与连接表的price列相对应。
Also, itemPriceMap object is a value type map, thus we must use the @ElementCollection annotation.
另外,itemPriceMap对象是一个值型地图,因此我们必须使用@ElementCollection注解。
In addition to basic value type objects, @Embeddable objects can also be used as the Map‘s values in a similar fashion.
除了基本的值类型对象外,@Embeddable对象也可以以类似的方式作为Map的值使用。
4. Using @MapKey
4.使用@MapKey
As we all know, requirements changes over time — so, now, let’s say we need to store some more attributes of Item along with itemName and itemPrice:
我们都知道,需求会随着时间的推移而改变–所以,现在,让我们说我们需要在Item和itemName以及itemPrice之外再存储一些Item的属性。
@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
}
Accordingly, let’s change Map<String, Double> to Map<String, Item> in the Order entity class:
因此,让我们把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;
}
Note that this time, we’ll use the @MapKey annotation so that Hibernate will use Item#itemName as the map key column instead of introducing an additional column in the join table. So, in this case, the join table order_item_mapping doesn’t have a key column — instead, it refers to the Item‘s name.
请注意,这次我们将使用 @MapKey 注释,这样 Hibernate 将使用 Item#itemName 作为映射键列,而不是在连接表中引入一个额外的列。因此,在这种情况下,连接表order_item_mapping没有键列–相反,它引用了Item的名称。
This is in contrast to @MapKeyColumn. When we use @MapKeyColumn, the map key resides in the join table. This is the reason why we can’t define our entity mapping using both the annotations in conjunction.
这与@MapKeyColumn相反。当我们使用@MapKeyColumn时,映射键位于连接表中。这就是为什么我们不能同时使用这两个注解来定义我们的实体映射的原因。
Also, itemMap is an entity type map, therefore we have to annotate the relationship using @OneToMany or @ManyToMany.
另外,itemMap是一个实体类型地图,因此我们必须使用@OneToMany或者@ManyToMany来注释关系。
5. Using @MapKeyEnumerated and @MapKeyTemporal
5.使用@MapKeyEnumerated和@MapKeyTemporal
Whenever we specify an enum as the Map key, we use @MapKeyEnumerated. Similarly, for temporal values, @MapKeyTemporal is used. The behavior is quite similar to the standard @Enumerated and @Temporal annotations respectively.
每当我们指定一个枚举作为Map键时,我们使用@MapKeyEnumerated。类似地,对于时间性的值,我们使用@MapKeyTemporal。其行为与标准的@Enumerated和@Temporal注释分别很相似。
By default, these are similar to @MapKeyColumn in that a key column will be created in the join table. If we want to reuse the value already stored in the persisted entity, we should additionally mark the field with @MapKey.
默认情况下,这些与@MapKeyColumn类似,将在连接表中创建一个键列。如果我们想重用已经存储在持久化实体中的值,我们应该另外用@MapKey标记该字段。
6. Using @MapKeyJoinColumn
6.使用@MapKeyJoinColumn
Next, let’s say we also need to keep track of the seller of each item. One way we might do this is to add a Seller entity and tie that to our Item entity:
接下来,让我们假设我们还需要跟踪每个项目的卖家。我们可以做的一个方法是添加一个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
}
In this case, let’s assume our use-case is to group all Order‘s Items by Seller. Hence, let’s change Map<String, Item> to Map<Seller, Item>:
在这种情况下,让我们假设我们的用例是将所有Order的Items按Seller分组。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
}
We need to add @MapKeyJoinColumn to achieve this since that annotation allows Hibernate to keep the seller_id column (the map key) in the join table order_item_mapping along with the item_id column. So then, at the time of reading the data from the database, we can perform a GROUP BY operation easily.
我们需要添加@MapKeyJoinColumn 来实现这一目标,因为该注解允许Hibernate在连接表order_item_mapping 中保留seller_id 列(映射键)以及item_id列。因此,在从数据库读取数据时,我们可以轻松地执行GROUP BY操作。
7. Conclusion
7.结语
In this article, we learned about the several ways of persisting Map in Hibernate depending upon the required mapping.
在这篇文章中,我们了解了在Hibernate中持久化Map的几种方法,这取决于所需的映射。
As always, the source code of this tutorial can be found over Github.
一如既往,本教程的源代码可以在Github上找到。