使用 EntityManager 对实体设置悲观锁
Set pessimistic lock on entity with EntityManager
考虑以下情况:
我们收到来自更新我们实体的网络服务的请求。有时我们可能会(几乎)同时收到两个请求。由于并发更新,我们遇到过实体看起来完全错误的情况。这个想法是悲观地锁定实体,这样每当第一个请求到来时它立即锁定实体并且第二个请求不能触及它(乐观锁定对我们来说是别无选择)。我写了一个集成测试来检查这个行为。
我得到了如下所示的集成测试:
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 并将其保存到数据库时,就会获取锁。但这不是我需要的。我需要锁而不修改实体。
- 我也尝试了
entityManager.lock(entity, LockModeType.PESSIMISTIC_WRITE)
方法,但行为是一样的。
- 我在 DBVisualizer 中找到了事务设置。目前它被设置为 TRANSACTION_NONE。我也尝试了所有其他(TRANSACTION_READ_UNCOMMITTED、TRANSACTION_READ_COMMITTED、TRANSACTION_REPEATABLE_READ、TRANSACTION_SERIALIZABLE),但都没有成功。
- 让第一个线程读取实体,然后第二个线程读取同一个实体。让第一个胎面修改实体,然后第二个胎面修改它。然后让双方都保存实体,最后保存实体的人获胜,不会抛出任何异常。
我如何悲观地读取一个对象,这意味着:每当我从数据库加载一个实体时,我希望它立即被锁定(即使没有修改)。
仅当另一个线程已持有锁时,锁才会失败。您可以在数据库中的单行上使用两个 FOR UPDATE
锁,因此这不是特定于 JPA 的东西。
你描述的两种方式,即。
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
您是否尝试过在您的应用程序服务器中设置隔离级别?
无论您之后尝试做什么,要锁定一行 (read/write),您需要将隔离级别设置为 TRANSACTION_SERIALIZABLE。
考虑以下情况: 我们收到来自更新我们实体的网络服务的请求。有时我们可能会(几乎)同时收到两个请求。由于并发更新,我们遇到过实体看起来完全错误的情况。这个想法是悲观地锁定实体,这样每当第一个请求到来时它立即锁定实体并且第二个请求不能触及它(乐观锁定对我们来说是别无选择)。我写了一个集成测试来检查这个行为。
我得到了如下所示的集成测试:
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 并将其保存到数据库时,就会获取锁。但这不是我需要的。我需要锁而不修改实体。 - 我也尝试了
entityManager.lock(entity, LockModeType.PESSIMISTIC_WRITE)
方法,但行为是一样的。 - 我在 DBVisualizer 中找到了事务设置。目前它被设置为 TRANSACTION_NONE。我也尝试了所有其他(TRANSACTION_READ_UNCOMMITTED、TRANSACTION_READ_COMMITTED、TRANSACTION_REPEATABLE_READ、TRANSACTION_SERIALIZABLE),但都没有成功。
- 让第一个线程读取实体,然后第二个线程读取同一个实体。让第一个胎面修改实体,然后第二个胎面修改它。然后让双方都保存实体,最后保存实体的人获胜,不会抛出任何异常。
我如何悲观地读取一个对象,这意味着:每当我从数据库加载一个实体时,我希望它立即被锁定(即使没有修改)。
仅当另一个线程已持有锁时,锁才会失败。您可以在数据库中的单行上使用两个 FOR UPDATE
锁,因此这不是特定于 JPA 的东西。
你描述的两种方式,即。
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
您是否尝试过在您的应用程序服务器中设置隔离级别?
无论您之后尝试做什么,要锁定一行 (read/write),您需要将隔离级别设置为 TRANSACTION_SERIALIZABLE。