Hibernate中持久化Map简介
1. 简介
在 Hibernate 中,我们可以通过将其中一个字段设为List来表示 Java bean 中的一对多关系 。
在这个快速教程中,我们将探索使用Map来实现此目的的各种方法。
2. Map与List不同
使用Map表示一对多关系与List不同,因为我们有一个键。
这个键将我们的实体关系变成了三元关联,其中每个键都指向一个简单的值或一个可嵌入的对象或一个实体。因此,要使用Map,我们总是需要一个连接表来存储引用父实体的外键——键和值。
但是这个连接表将与其他连接表有点不同,**因为主键不一定是父和目标的外键。**相反,我们将主键组合为父外键和作为Map 键的列的组合。
Map中的键值对可能有两种类型:Value Type 和 Entity Type 。在接下来的部分中,我们将研究在 Hibernate 中表示这些关联的方法。
3. 使用*@MapKeyColumn*
假设我们有一个 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
}
**我们需要向 Hibernate 指明从哪里获取键和值。**对于键,**我们使用了 @MapKeyColumn,**表示Map的键是我们连接表的item_name列order_item_mapping。同样,@Column指定 Map 的值对应于连接表的price列。
另外,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
}
因此,让我们在 Order实体类中将Map<String, Double>更改为Map<String, 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")})
@MapKey(name = "itemName")
private Map<String, Item> itemMap;
}
请注意,这一次,我们将使用*@MapKey注解,以便 Hibernate 将使用Item#itemName作为映射键列,而不是在连接表中引入额外的列。因此,在这种情况下,**连接表order_item_mapping没有键列**。相反,它引用Item*的名称。
这与*@MapKeyColumn* 形成对比。当我们使用 @MapKeyColumn 时,映射键驻留在连接表中。这就是为什么我们不能同时使用这两个注解来定义我们的实体映射的原因。
此外,itemMap是一个实体类型映射,因此我们必须使用 @OneToMany 或*@ManyToMany *来注解关系。
5. 使用*@MapKeyEnumerated和@MapKeyTemporal*
每当我们将枚举指定为Map键时,我们都会使用*@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
}
在这种情况下,假设我们的用例是 按Seller对所有Order的Item进行分组。因此,让我们将*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 将seller_id列(映射键) 与 item_id 列一起保留在连接表order_item_mapping*中。那么,在从数据库中读取数据的时候,我们可以很方便的进行 GROUP BY 操作。