我知道有很多类似的帖子,但我找不到一个明确的答案来解决我的问题。
为了尽可能简单,假设我有这样一个实体:
@Entity
public class Person implements Serializable {
@Id
private Long id; // PK
private String name; // business key
/* getters and setters */
/*
override equals() and hashCode()
to use the **name** field
*/
}
因此,id
是PK,name
是业务密钥。假设我得到一个名称列表,其中可能有重复项,我想存储这些名称。如果我只是为每个名称创建一个对象,并让JPA使其持久化,那么我的最终表将包含重复的名称——这是不可接受的。
我的问题是,考虑到我在下面描述的替代方案和(特别欢迎)你自己的方案,你认为什么是最好的方法。
可能的解决方案1:检查实体管理器
在创建新的人员对象之前,请检查是否已经管理了具有相同人员名称的对象。问题:实体经理只能通过PK查询。是否有我不知道的解决方法?
可能的解决方案2:通过查询查找对象
Query query = em.createQuery("SELECT p FROM Person p WHERE p.name = ...");
List<Person> list = query.getResultList();
问题:如果请求的对象已经加载到em中,这还会从数据库中获取吗?如果是这样的话,我想如果频繁地进行,由于解析查询,它仍然不会很高效?
可能的解决方案3:保留一本单独的词典
这是可能的,因为equals()和hashCode()被重写以使用字段name
。
Map<String,Person> personDict = new HashMap<String,Person>();
for(String n : incomingNames) {
Person p = personDict.get(n);
if (p == null) {
p = new Person();
p.setName(n);
em.persist(p);
personDict.put(n,p);
}
// do something with it
}
问题1:为大型集合浪费内存,因为这本质上是实体管理器所做的(但不完全是!)
问题2:假设我有一个更复杂的模式,并且在初次编写之后,我的应用程序关闭,重新启动,并且需要重新加载数据库。如果所有表都显式加载到em中,那么我可以很容易地重新填充字典(每个实体一个),但如果我使用惰性提取和/或级联读取,那么就不那么容易了。
我最近开始使用JPA(我使用EclipseLink),所以也许我在这里缺少了一些基本的东西,因为这个问题似乎可以归结为一种非常常见的使用模式。
请开导我!
我能想到的最好的解决方案非常简单,使用唯一约束
@Entity
@UniqueConstraint(columnNames="name")
public class Person implements Serializable {
@Id
private Long id; // PK
private String name; // business key
}
确保字段可以(正确)用作键的唯一方法是在字段上创建一个唯一的约束。您可以使用@UniqueConstraint(columnNames="name")
或@Column(unique = true)
来执行此操作。
在尝试插入重复密钥时,EntityManager(实际上是DB)将引发异常。这种情况也适用于手动设置的主键。
防止异常的唯一方法是在键上进行选择并检查它是否存在。