首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用EntityManager设置实体的悲观锁

用EntityManager设置实体的悲观锁
EN

Stack Overflow用户
提问于 2018-02-05 09:02:36
回答 3查看 6.3K关注 0票数 2

考虑以下情况:我们收到来自web服务的请求,该服务更新我们的实体。有时我们可能(几乎)同时收到两个请求。我们遇到的情况是,由于并发更新,我们的实体看起来完全错误。这样做的想法是将实体锁定为悲观的,这样每当第一个请求出现时,它就会立即锁定实体,而第二个请求就无法触摸它(乐观锁定对我们来说是没有选择的)。我编写了一个集成测试来检查这种行为。

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

代码语言:javascript
复制
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();
}

它叫豆子

代码语言:javascript
复制
@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

代码语言:javascript
复制
@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.");
    }
}

使用

代码语言:javascript
复制
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没有共享他的hibernate上下文。

我提出以下意见:

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

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

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2018-02-15 13:01:36

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

要获得行上的锁,无论以后要做什么(读/写),都需要将隔离级别设置为TRANSACTION_SERIALIZABLE。

票数 0
EN

Stack Overflow用户

发布于 2018-02-14 08:53:17

你描述的两种方式。

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

在数据库中的相关行上持有一个锁,但只在entityManager生命周期内持有,即。对于封闭事务的时间,一旦您到达事务结束,锁就会自动释放。

代码语言:javascript
复制
@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
EN

Stack Overflow用户

发布于 2018-02-05 10:40:54

只有当另一个线程已经持有锁时,锁才会失败。您可以在DB中的单行上使用两个FOR UPDATE锁,所以这不是JPA特有的东西。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/48618870

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档