【问题标题】:Singleton-EJB concurency with a container-managed transactionSingleton-EJB 并发与容器管理的事务
【发布时间】:2013-10-07 11:03:49
【问题描述】:

我有一个 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)。但是在数据库中设置状态的事务是在锁被释放后提交的,下一个调用者可能会再次抓取相同的对象。

我怎样才能防止这种情况发生?

【问题讨论】:

  • 了解事务隔离级别。
  • 为什么不让doSomeStuffAndClose同步?
  • @frostjogla 感谢您的评论,这很有趣。所以解决方案是将隔离级别设置为“可序列化”。但这不是我的选择,我的 bean 使用共享连接。
  • @siledh 在 java EE 中的容器你不能使用同步关键字
  • @Chechus 因为这会被认为是不好的做法,还是有任何形式上的限制?我不明白为什么同步在这里不起作用,它会实现 OP 想要的一切。

标签: java jakarta-ee concurrency transactions singleton


【解决方案1】:

在 JPA 中没有办法做到这一点(所以,以可移植的方式)。您的选择可能是:

  1. 一些 JPA 实现允许在每个查询的基础上设置隔离级别(例如 OpenJPA),有些则不允许(休眠)。但即使在 OpenJPA 中,此提示也需要在特定的数据库驱动程序中实现,否则无效)。
  2. 运行本机查询 - 有关详细信息,请参阅您的数据库文档。

作为旁注,我应该说 JPA(以及一般的 Java EE)在设计时并没有考虑到批量数据库操作 - 它是针对在大多数情况下不重叠的数据项的多个并发查询。

【讨论】:

    【解决方案2】:

    您可以从您的 doSomeStuffAndClose 方法 Stateful Session Bean 调用,并实现 SessionSynchronization 接口。与 SFSB 中的 afterCompletion 方法相比,您可以通知单例 bean 数据已提交并且可以处理另一个请求。

    我知道这样我们有两个非常紧密耦合的 bean,但这应该可以解决您的问题。

    【讨论】:

    • 我认为 OP 是为了避免其他 bean(任何类型)访问任何 MyObj 记录的可能性 - 在这种情况下,您的建议无效。但也许我误解了它。
    • @MaDa 您可以将 SFSB 视为实用程序 bean,仅用于处理事务。我只将访问约束解释为并发访问预防。
    • 这将是一个可能的解决方案(SessionSynchronization 对我来说是新的,谢谢......)但这种方法对我来说似乎有点复杂。我想我会通过使用 bean 管理的事务自己处理事务来解决我的问题...
    【解决方案3】:

    您可以使用 SELECT FOR UPDATE 来序列化行的访问。

    在 JPA 2 中使用 LockModeType: http://docs.oracle.com/javaee/6/api/javax/persistence/LockModeType.html

    q.setLockMode(LockModeType.PESSIMISTIC_WRITE)
    

    【讨论】:

    • 顺便说一句。 @MaDa 的答案仅对 JPA 1.0 有效。自 JPA 2.0 以来,LockModeType 可用于查询或多个 EntityManger 方法(例如刷新、查找...)
    【解决方案4】:

    您正在使用容器管理的并发(默认)。在 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)。

    【讨论】:

      【解决方案5】:

      如果您使用的是 Wildfly:这是一个错误。 https://issues.jboss.org/browse/WFLY-4844 描述了您的问题,该问题将在 Wildfly 10 中修复。该问题被描述为计时器问题,可能与您的问题相同。

      我的解决方法是将执行工作的代码分离到另一个由外部(计时器)bean 调用的 bean 中。外部 bean 方法被注释为不启动事务 (@TransactionAttribute(TransactionAttributeType.NEVER)),因此事务在第二个新 bean 中启动并安全完成。

      【讨论】:

      • 如果需要,我可以提供我的解决方案中的代码片段(解决方法)。
      • 您能提供代码吗?你把 getAllOpenObjects 放到另一个 bean 上了吗?
      猜你喜欢
      • 1970-01-01
      • 2011-11-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-25
      • 2015-11-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多