我有一个Singleton-EJB,它从具有特定状态的数据库中读取所有对象。然后我用这些对象做一些事情并将状态设置为其他东西:
@Singleton
public class MyEJB {
@PersistenceContext(unitName = "MyPu")
private EntityManager em;
@Lock(LockType.WRITE)
public void doSomeStuffAndClose() {
List<MyObj> objects = getAllOpenObjects();
for (MyObj obj : objects) {
// do some stuff here...
obj.setClosed(true);
}
}
private List<MyObj> getAllOpenObjects() {
TypedQuery<MyObj> q = em.createQuery("select o from MyObj o "
+ "where o.closed = false", MyObj.class);
return q.getResultList();
}
}
现在,如果我想确保无法快速调用我的方法,我会添加注释@Lock(LockType.WRITE)
。但是,在锁定释放后提交设置数据库中状态的事务,并且下一个调用者可能会再次抓取相同的对象。
我怎么能阻止这个?
答案 0 :(得分:2)
如果您使用Wildfly:这是一个错误。 https://issues.jboss.org/browse/WFLY-4844描述了您将在Wildfly 10中修复的问题。问题被描述为计时器问题,可能与您的问题相同。
我的解决方法是将执行该工作的代码分离到另一个由外部(计时器)bean调用的bean中。外部bean方法注释为不启动事务(@TransactionAttribute(TransactionAttributeType.NEVER)
),因此事务在第二个新bean中启动并安全地完成。
答案 1 :(得分:1)
您可以使用SELECT FOR UPDATE来序列化对行的访问。
使用JPA 2时,请使用LockModeType: http://docs.oracle.com/javaee/6/api/javax/persistence/LockModeType.html
q.setLockMode(LockModeType.PESSIMISTIC_WRITE)
答案 2 :(得分:0)
在JPA中无法做到这一点(因此,以便携式方式)。您的选择可能是:
作为旁注,我应该说JPA(以及一般的Java EE)并没有考虑到批量数据库操作 - 而是针对数据项的多个并发查询,在大多数情况下不会重叠。
答案 3 :(得分:0)
您可以使用已实现的SessionSynchronization接口从doSomeStuffAndClose方法调用Stateful Session Bean。与SFSB中的afterCompletion方法相比,您可以通知singleton bean数据已被提交并可以处理另一个请求。
我知道这样我们有两个非常紧密耦合的bean,但这应该可以解决你的问题。
答案 4 :(得分:0)
您正在使用容器管理的并发(默认值)。在JavaEE 7中(不确定旧的,但可能是肯定的),事务保证在方法退出之前提交,因此在释放锁之前。从JavaEE 7教程:
“通常,容器在企业bean方法启动之前立即开始事务,并在方法退出之前提交事务。每个方法都可以与单个事务关联。在方法中不允许嵌套或多个事务。” https://docs.oracle.com/javaee/7/tutorial/doc/transactions003.htm#BNCIJ
如果您遇到其他行为,请检查可能处于活动状态的任何缓存(@Cacheable)。您可以在此处观看另一个有趣的问题:https://stackoverflow.com/questions/26790667/timeout-and-container-managed-concurrency-in-singleton
顺便说一下,LockType(WRITE)也是默认的,你不需要显式它。因此,getAllObjects也是LockType(WRITE)。