使用EntityManager在实体上设置悲观锁定

时间:2018-02-05 09:02:36

标签: java hibernate jpa-2.0 informix

考虑以下情况: 我们收到来自更新我们实体的网络服务的请求。有时我们可能会(几乎)同时收到两个请求。由于并发更新,我们的实体看起来完全错误。想法是锁定实体悲观,以便每当第一个请求到来时它立即锁定实体,第二个请求不能触及它(乐观锁定对我们来说是不可替代的)。我写了一个集成测试来检查这种行为。

我得到了一个集成测试,如下所示:

protected static TestRemoteFacade testFacade;

@BeforeClass
public static void setup() {
    testFacade = BeanLocator.lookupRemote(TestRemoteFacade.class, TestRemoteFacade.REMOTE_JNDI_NAME, TestRemoteFacade.NAMESPACE);
}

@Test
public void testPessimisticLock() throws Exception {
    testFacade.readPessimisticTwice();
}

调用bean

@Stateless
@Clustered
@SecurityDomain("myDomain")
@RolesAllowed({ Roles.ACCESS })
public class TestFacadeBean extends FacadeBean implements TestRemoteFacade {

    @EJB
    private FiolaProduktLocalFacade produkt;

    @Override
    public void readPessimisticTwice() {
        produkt.readPessimisticTwice();
    }
}

produkt是一个bean本身

@Stateless
@Clustered
@SecurityDomain("myDomain")
@RolesAllowed({ Roles.ACCESS })
public class ProduktFacadeBean implements ProduktLocalFacade {

    @Override
    public void readPessimisticTwice() {
        EntityManager entityManager = MyService.getCrudService().getEntityManager();
        System.out.println("Before first try.");
        entityManager.find(MyEntity.class, 1, LockModeType.PESSIMISTIC_WRITE);
        System.out.println("Before second try.");
        entityManager.find(MyEntity.class, 1, LockModeType.PESSIMISTIC_WRITE);
        System.out.println("After second try.");
    }
}

public class MyService {

    public static CrudServiceLocalFacade getCrudService() {
        return CrudServiceLookup.getCrudService();
    }

}

public final class CrudServiceLookup {

    private static CrudServiceLocalFacade crudService;

    private CrudServiceLookup(){
    }

    public static CrudServiceLocalFacade getCrudService() {
        if (crudService == null)
            crudService = BeanLocator.lookup(CrudServiceLocalFacade.class, CrudServiceLocalFacade.LOCAL_JNDI_NAME);
        return crudService;
    }

    public static void setCrudService(CrudServiceLocalFacade crudService) {
        CrudServiceLookup.crudService = crudService;
    }

}

@Stateless
@Local(CrudServiceLocalFacade.class)
@TransactionAttribute(TransactionAttributeType.MANDATORY)
@Interceptors(OracleDataBaseInterceptor.class)
public class CrudServiceFacadeBean implements CrudServiceLocalFacade {

    private EntityManager em;

    @Override
    @PersistenceContext(unitName = "persistence_unit")
    public void setEntityManager(EntityManager entityManager) {
        em = entityManager;
    }

    @Override
    public EntityManager getEntityManager() {
        return em;
    }

}

现在出现的问题是:如果我使用System.out.println("Before second try.");处的断点启动集成测试一次,然后再次启动集成测试,后者仍然可以读取MyEntity。值得注意的是,它们是不同的实例(我在调试模式下对instanceId进行了观察)。这表明entityManager没有分享他的休眠背景。

我做了以下观察:

  • 每当我在entity上调用一个setter并将其保存到db时,就会获得锁定。但这不是我需要的。我没有修改实体就需要锁。
  • 我也试过了方法entityManager.lock(entity, LockModeType.PESSIMISTIC_WRITE),但行为是一样的。
  • 我在DBVisualizer中找到了交易设置。目前它被设置为TRANSACTION_NONE。我也尝试了所有其他的(TRANSACTION_READ_UNCOMMITTED,TRANSACTION_READ_COMMITTED,TRANSACTION_REPEATABLE_READ,TRANSACTION_SERIALIZABLE),没有任何成功。
  • 让第一个线程读取实体,然后第二个线程读取同一个实体。让第一个胎面修改实体然后第二个修改它。然后让两者都保存实体,并且保存实体的人最后获胜并且不会抛出异常。

如何读取悲观对象,这意味着:每当我从db加载实体时,我希望它立即被锁定(即使没有修改)。

3 个答案:

答案 0 :(得分:1)

您描述的两种方式即

  • em.find(MyEntity.class, 1, LockModeType.PESSIMISTIC_WRITE)
  • em.lock(entity, LockModeType.PESSIMISTIC_WRITE)

对数据库中的相关行进行锁定,但仅对entityManager生命周期进行锁定,即。在封闭交易的时间,一旦您到达交易结束,锁将自动释放

@Transactional()
public void doSomething() {
  em.lock(entity, LockModeType.PESSIMISTIC_WRITE); // entity is locked
  // any other thread trying to update the entity until this method finishes will raise an error
}
...
object.doSomething();
object.doSomethingElse(); // lock is already released here

答案 1 :(得分:0)

仅当另一个线程已经持有锁时,锁才会失败。您可以在DB中的单行上使用两个FOR UPDATE锁,因此它不是JPA特定的。

答案 2 :(得分:0)

您是否尝试在应用程序服务器中设置隔离级别?

无论你之后想要做什么(读/写),都要锁定一行,你需要将隔离级别设置为TRANSACTION_SERIALIZABLE。