考虑以下情况: 我们收到来自更新我们实体的网络服务的请求。有时我们可能会(几乎)同时收到两个请求。由于并发更新,我们的实体看起来完全错误。想法是锁定实体悲观,以便每当第一个请求到来时它立即锁定实体,第二个请求不能触及它(乐观锁定对我们来说是不可替代的)。我写了一个集成测试来检查这种行为。
我得到了一个集成测试,如下所示:
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)
,但行为是一样的。如何读取悲观对象,这意味着:每当我从db加载实体时,我希望它立即被锁定(即使没有修改)。
答案 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。