使用 EntityManager 在实体上设置悲观锁定
Posted
技术标签:
【中文标题】使用 EntityManager 在实体上设置悲观锁定【英文标题】:Set pessimistic lock on entity with EntityManager 【发布时间】:2018-02-05 09:02:36 【问题描述】:考虑以下情况: 我们收到来自更新我们实体的网络服务的请求。有时我们可能会(几乎)同时收到两个请求。由于并发更新,我们的实体看起来完全错误。这个想法是悲观地锁定实体,这样每当第一个请求到来时,它会立即锁定实体,而第二个请求不能触及它(乐观锁定对我们来说是没有选择的)。我编写了一个集成测试来检查这种行为。
我得到了一个如下所示的集成测试:
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),但均未成功。
让第一个线程读取实体,然后第二个线程读取相同的实体。让第一个踏板修改实体,然后第二个修改它。然后让双方都保存实体,最后保存实体的人获胜,不会抛出异常。
我怎样才能读取一个悲观的对象,这意味着:每当我从数据库中加载一个实体时,我希望它立即被锁定(即使没有修改)。
【问题讨论】:
我不知道它在 Java、Hibernate、JPA 中是如何应用的,但是——如果您的软件打开游标,该游标使用 CURSOR STABILITY 或 REPEATABLE READ 隔离来获取行(又名实体) (SET ISOLATION 语句)和 FOR UPDATE 模式(光标属性),则该行将在获取时被其他人锁定以防更新。在您进行更改之前,您不会阻止其他人阅读该行,但您可以确保它不会在您背后改变。但是,这是在相对较低的级别上工作的(我认为是 ESQL/C;您认为是 JDBC 或 ODBC)。我不知道它是如何向上转换调用链的。 @JonathanLeffler:我将事务级别更改为 TRANSACTION_READ_UNCOMMITED、TRANSACTION_READ_COMMITED、TRANSACTION_REPEATABLE_READ 和 TRANSACTION_SERIALIZABLE。他们都没有工作。我在第二次阅读时没有得到异常(未经修改)。 @Chris311 你应该发布更完整的代码来测试它。是否使用两个不同的实体管理器读取实体?否则,每当您再次尝试加载同一实体时,实体管理器只会返回它的本地托管实例,它不再需要访问数据库。 以下内容可能对***.com/questions/32336481/…有帮助 EntityManager 不是线程安全的,具有单例范围的 EM 看起来确实有点设计味道(除非您的应用是单线程的) 【参考方案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
【讨论】:
这正是我面临的问题。我得到了相同的实体管理器实例并且我在同一个事务中。我更新了我的问题以使这一点更清楚。【参考方案2】:您是否尝试在应用服务器中设置隔离级别?
无论您之后尝试做什么(读/写),要锁定一行,您需要将隔离级别设置为 TRANSACTION_SERIALIZABLE。
【讨论】:
【参考方案3】:只有当另一个线程已经持有锁时,锁才会失败。您可以在 DB 中的单行上使用两个 FOR UPDATE
锁,因此这不是 JPA 特定的东西。
【讨论】:
根本没有回答我的问题。 @Chris311 你做对了!entityManager.lock(entity, LockModeType.PESSIMISTIC_WRITE);
就够了
不,它不是这样工作的。我可以毫无例外地调用 find 方法两次。此外,我可以使用 DBVisualizer 读取数据库中的实体...
1.您从一个线程进行后续调用吗?如果是 - 没关系。 2.你不能锁定实体的读取,只能从修改
@Chris311 您是否尝试从 dbvisualizer 更改实体,同时将其锁定在您的应用程序中?以上是关于使用 EntityManager 在实体上设置悲观锁定的主要内容,如果未能解决你的问题,请参考以下文章