我使用Java 8,Hibernate 5.1.0.Final,Guice 4.1.0,Jersey 1.19。我有几个REST方法可以修改同一个对象 - 从数据库加载的实体。当它们同时运行并修改相同的项目时,我得到:
javax.persistence.RollbackException: Error while committing the transaction
at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:86)
Caused by: javax.persistence.OptimisticLockException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
Caused by: org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
可能的解决方案:
OptimisticLockException
(5 Common Hibernate Exceptions and How to Fix Them)OptimisticLockException
(JPA: pattern for handling OptimisticLockException)还有其他选择吗?什么是处理这个问题的最佳方法?
更新1
我试图实施重试:
public void changeItemName(Long id, String name){
Item item = itemDAO.find(id);
item.setName(name);
try {
itemDAO.save(item);
} catch (RollbackException | OptimisticLockException | StaleStateException e) {
logger.warn("Retry method after " + e.getClass().getName());
changeItemName(id, name);
}
}
但我明白了:
javax.persistence.RollbackException: Error while committing the transaction
at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:86)
Caused by: javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: could not update: [com.example.Item#1]
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1692)
Caused by: org.hibernate.exception.GenericJDBCException: could not update: [com.example.Item#1]
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:47)
Caused by: org.postgresql.util.PSQLException: This statement has been closed.
at org.postgresql.jdbc2.AbstractJdbc2Statement.checkClosed(AbstractJdbc2Statement.java:2653)
答案 0 :(得分:1)
在REST方法中按对象进行同步我会说这是不可能的。想象一下,在2个节点上运行相同的服务。
再次不断刷新对象状态不是一个选项。更新可能发生在2次刷新之间。
这个很好。 CAS原理。虽然版本不正确但尝试保存更改。但实际上这取决于您的业务逻辑。例如。有一个User对象,初始名称为" name1"。第一个电话会从" name1"到" name2"。第二次调用试图改变" name1"到" name3"。我们可以(A)更改名称,禁止" name1" - >" name2"更新或(B)拒绝更改报告给调用者刷新以查看实际状态并将更改应用于实际状态。
如果我们尝试修改,例如账户余额(A)方法无效,我们需要(B)。
您应该根据您需要的业务逻辑决定做什么。
更新: 它不会以这种方式工作。您需要更高地捕获它 - 在事务开始的地方并在单独的事务中执行它。
假设你有
@Service
public class MyServiceImpl
@Transactional
public void changeItemName(Long id, String name) {
}
可能发生乐观锁定异常。
如果仅从catch
块调用该方法,则不会启动新事务,因为不会调用proxies方法。
相反,你可以做这样的事情
@Service
public class MyServiceImpl
@Autowired
private MyService myService;
@Transactional
public void changeItemName(Long id, String name) {
catch() {
myService.changeItemName(id, name);
}
}