使用集合更新实体的 JPA 最佳实践
我在 Glassfish 容器中使用 JPA。我有以下模型(不完整)
@Entity
public class Node {
@Id
private String serial;
@Version
@Column(updatable=false)
protected Integer version;
private String name;
@ManyToMany(cascade = {CascadeType.PERSIST,CascadeType.MERGE})
private Set<LUN> luns = new HashSet<LUN>();
@Entity
public class LUN {
@Id
private String wwid;
@Version
@Column(updatable=false)
protected Integer version;
private String vendor;
private String model;
private Long capacity;
@ManyToMany(mappedBy = "luns")
private Set<Node> nodes = new HashSet<Node>();
此信息将每天更新。现在我的问题是,执行此操作的最佳实践是什么。
我的第一个方法是,我每天在客户端上生成新的节点对象(带有 LUN),并通过服务将其合并到数据库(我想让 JPA 来做这项工作)。
现在我做了一些没有 LUN 的测试。我在无状态 EJB 中有以下服务:
public void updateNode(Node node) {
if (!nodeInDB(node)) {
LOGGER.log(Level.INFO, "persisting node {0} the first time", node.toString());
em.persist(node);
} else {
LOGGER.log(Level.INFO, "merging node {0}", node.toString());
node = em.merge(node);
}
}
测试:
@Test
public void addTest() throws Exception {
Node node = new Node();
node.setName("hostname");
node.setSerial("serial");
nodeManager.updateNode(node);
nodeManager.updateNode(node);
node.setName("newhostname");
nodeManager.updateNode(node);
}
无需 @Version 字段即可工作。通过 @Version 字段,我得到了 OptimisticLockException。
这是错误的做法吗?我是否必须始终执行 em.find(...) 然后通过 getter 和 setter 修改托管实体?
任何帮助表示赞赏。
BR雷内
I am using JPA in a Glassfish Container. I have the following Modell (not complete)
@Entity
public class Node {
@Id
private String serial;
@Version
@Column(updatable=false)
protected Integer version;
private String name;
@ManyToMany(cascade = {CascadeType.PERSIST,CascadeType.MERGE})
private Set<LUN> luns = new HashSet<LUN>();
@Entity
public class LUN {
@Id
private String wwid;
@Version
@Column(updatable=false)
protected Integer version;
private String vendor;
private String model;
private Long capacity;
@ManyToMany(mappedBy = "luns")
private Set<Node> nodes = new HashSet<Node>();
This information will be updated daily. Now my question is, what is the best practice to do this.
My fist approach was, I generate the Node Objects on the client (with LUNs) every day new, and merge it to the Database (I wanted to let JPA do the work) via service.
Now I did some tests without LUNs yet. I have the following service in a stateless EJB:
public void updateNode(Node node) {
if (!nodeInDB(node)) {
LOGGER.log(Level.INFO, "persisting node {0} the first time", node.toString());
em.persist(node);
} else {
LOGGER.log(Level.INFO, "merging node {0}", node.toString());
node = em.merge(node);
}
}
The test:
@Test
public void addTest() throws Exception {
Node node = new Node();
node.setName("hostname");
node.setSerial("serial");
nodeManager.updateNode(node);
nodeManager.updateNode(node);
node.setName("newhostname");
nodeManager.updateNode(node);
}
This works without the @Version Field. With the @Version field I get an OptimisticLockException.
Is that the wrong approach? Do I have to always perform an em.find(...) and then modify the managed entity via getter and setter?
Any help is appreciated.
BR Rene
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
@version 注解用于启用乐观锁定。
当您使用乐观锁定时,每次成功写入表都会增加一个版本计数器,每次持久化实体时都会读取并比较该计数器。如果您第一次发现实体时读取的版本与写入时表中的版本不匹配,则会引发异常。
您的程序在仅读取一次版本列后多次更新该表。因此,在第二次调用 persist() 或 merge() 时,版本号不匹配,查询失败。这是使用乐观锁定时的预期行为:您试图覆盖自第一次读取以来已更改的行。
回答你的最后一个问题:每次写入数据库后,你需要读取更改的@version信息。您可以通过调用 em.refresh() 来完成此操作。
但是,您应该考虑重新考虑您的策略:乐观锁最好在事务上使用,以确保用户执行更改时数据的一致性。这些通常读取数据,将其显示给用户,等待更改,然后在用户完成任务后保留数据。在这种情况下,您实际上并不希望也不需要多次写入相同的数据行,因为事务可能会由于每一个写入调用上的乐观锁定而失败 - 这会使事情变得复杂而不是让事情变得更简单。
The @version annotation is used to enable optimistic locking.
When you use optimistic locking, each successful write to your table increases a version counter, which is read and compared every time you persist your entities. If the version read when you first find your entity doesn't match the version in the table at write time, an exception is thrown.
Your program updates the table several times after reading the version column only once. Therefore, at the second time you call persist() or merge(), the version numbers don't match, and your query fails. This is the expected behavior when using optimistic locking: you were trying to overwrite a row that was changed since the time you first read it.
To answer your last question: You need to read the changed @version information after every write to your database. You can do this by calling em.refresh().
You should, however, consider re-thinking your strategy: Optimistic locks are best used on transactions, to ensure data consistency while the user performs changes. These usually read the data, display it to the user, wait for changes, and then persist the data after the user has finished the task. You wouldn't really want nor need to write the same data rows several times in this context, because the transaction could fail due to optimistic locking on every one of these write calls - it would complicate things rather than make them more simple.