为什么我对使用Oracle的JPA中的悲观锁定无法正常工作

时间:2015-04-30 09:15:07

标签: java oracle hibernate jpa pessimistic-locking

我试图为在不同JBoss节点中运行的cron作业实现某种信号量。我尝试使用数据库(Oracle 11g)作为锁定机制,使用一个表来同步不同节点中的cron作业。 表非常简单:

CREATE TABLE SYNCHRONIZED_CRON_JOB_TASK
(
   ID            NUMBER(10)           NOT NULL,
   CRONJOBTYPE   VARCHAR2(255 Byte),
   CREATIONDATE  TIMESTAMP(6)         NOT NULL,
   RUNNING       NUMBER(1)
);

ALTER TABLE SYNCHRONIZED_CRON_JOB_TASK
   ADD CONSTRAINT PK_SYNCHRONIZED_CRON_JOB_TASK
   PRIMARY KEY (ID); 

因此,当作业开始时,它会在表中搜索其cronjobtype的条目,并检查它是否已在运行。如果不是,则将条目设置运行标志更新为true。第一个选择是使用JPA CriteriaApi使用Hibernate和Pessimistic Lock进行的。

query.setLockMode(javax.persistence.LockModeType.PESSIMISTIC_WRITE);

所有这些操作都是在一次交易中完成的。

当一个进程运行时,它所做的查询如下:

[Server:server-two] 10:38:00,049 INFO  [stdout] (scheduler-2) 2015-04-30 10:38:00,048 WARN  (Loader.java:264) - HHH000444: Encountered request for locking however dialect reports that database prefers locking be done in a separate select (follow-on locking); results will be locked after initial query executes
[Server:server-two] 10:38:00,049 INFO  [stdout] (scheduler-2) Hibernate: select * from ( select distinct synchroniz0_.id as id1_127_, synchroniz0_.creationDate as creation2_127_, synchroniz0_.running as running3_127_, synchroniz0_.CRONJOBTYPE as CRONJOBT4_127_ from SYNCHRONIZED_CRON_JOB_TASK synchroniz0_ where synchroniz0_.CRONJOBTYPE=? ) where rownum <= ?
[Server:server-two] 10:38:00,053 INFO  [stdout] (scheduler-2) Hibernate: select id from SYNCHRONIZED_CRON_JOB_TASK where id =? for update
[Server:server-two] 10:38:00,056 INFO  [stdout] (scheduler-2) Hibernate: update SYNCHRONIZED_CRON_JOB_TASK set creationDate=?, running=?, CRONJOBTYPE=? where id=?

此警告没有问题,您可以看到第一个选择然后选择更新,因此Oracle应该阻止此行上的其他选择操作。 但重要的是,查询没有被阻止,因此两个作业可以进入并进行选择和更新而不会出现问题。锁定不起作用,如果我们同时运行两个cron作业,我们可以看到它:

[Server:server-one] 10:38:00,008 INFO  [stdout] (scheduler-3) 2015-04-30 10:38:00,008 WARN  (Loader.java:264) - HHH000444: Encountered request for locking however dialect reports that database prefers locking be done in a separate select (follow-on locking); results will be locked after initial query executes
[Server:server-two] 10:38:00,008 INFO  [stdout] (scheduler-2) 2015-04-30 10:38:00,008 WARN  (Loader.java:264) - HHH000444: Encountered request for locking however dialect reports that database prefers locking be done in a separate select (follow-on locking); results will be locked after initial query executes
[Server:server-two] 10:38:00,009 INFO  [stdout] (scheduler-2) Hibernate: select * from ( select distinct synchroniz0_.id as id1_127_, synchroniz0_.creationDate as creation2_127_, synchroniz0_.running as running3_127_, synchroniz0_.CRONJOBTYPE as CRONJOBT4_127_ from SYNCHRONIZED_CRON_JOB_TASK synchroniz0_ where synchroniz0_.CRONJOBTYPE=? ) where rownum <= ?
[Server:server-one] 10:38:00,009 INFO  [stdout] (scheduler-3) Hibernate: select * from ( select distinct synchroniz0_.id as id1_127_, synchroniz0_.creationDate as creation2_127_, synchroniz0_.running as running3_127_, synchroniz0_.CRONJOBTYPE as CRONJOBT4_127_ from SYNCHRONIZED_CRON_JOB_TASK synchroniz0_ where synchroniz0_.CRONJOBTYPE=? ) where rownum <= ?
[Server:server-two] 10:38:00,013 INFO  [stdout] (scheduler-2) Hibernate: select id from SYNCHRONIZED_CRON_JOB_TASK where id =? for update
[Server:server-one] 10:38:00,014 INFO  [stdout] (scheduler-3) Hibernate: select id from SYNCHRONIZED_CRON_JOB_TASK where id =? for update
[Server:server-two] 10:38:00,016 INFO  [stdout] (scheduler-2) 2015-04-30 10:38:00,015 DEBUG (SynchronizedCronJobService.java:65) - Task read SynchronizedCronJobTask [id=185, type=AlertMailTaskExecutor, creationDate=2015-04-25 07:11:33.0, running=false]
[Server:server-two] 10:38:00,018 INFO  [stdout] (scheduler-2) Hibernate: update SYNCHRONIZED_CRON_JOB_TASK set creationDate=?, running=?, CRONJOBTYPE=? where id=?
[Server:server-one] 10:38:00,022 INFO  [stdout] (scheduler-3) 2015-04-30 10:38:00,022 DEBUG (SynchronizedCronJobService.java:65) - Task read SynchronizedCronJobTask [id=185, type=AlertMailTaskExecutor, creationDate=2015-04-25 07:11:33.0, running=false]
[Server:server-one] 10:38:00,024 INFO  [stdout] (scheduler-3) Hibernate: update SYNCHRONIZED_CRON_JOB_TASK set creationDate=?, running=?, CRONJOBTYPE=? where id=?

我已尝试使用两个连接在SQL工具(SQLWorkbenchJ)上进行此选择以进行更新,并且此工具中的bloking工作正常。但是如果我在SQL工具上选择更新并启动cron作业,那么它们就不会运行并且没有问题。

我认为问题来自JPA,Hibernate或Oracle驱动程序,但我不确定。有什么问题在哪里?我应该使用anotehr策略吗? 提前谢谢。

3 个答案:

答案 0 :(得分:4)

最后,我设法使其工作,但有一些修改。我们的想法是使用LockModeType.PESSIMISTIC_FORCE_INCREMENT而不是PESSIMISTIC_WRITE。使用此锁定模式,Cron Jobs的行为如下:

  1. 当第一个作业进行select for update时,一切都按预期进行,但对象上的版本发生了变化。
  2. 如果另一个作业尝试在第一个仍在其事务中时进行相同的选择,则JPA会启动一个OptimisticLockException,因此如果您捕获该异常,则可以确保它已被抛出以进行读锁定。
  3. 这个解决方案有各种各样的对应物:

    1. SynchronizedCronJobTask必须具有版本字段,并且受@Version
    2. 版本控制
    3. 您需要处理OptimisticLockException,它应该在事务服务方法之外捕获,以便在de lock发生时进行回滚。
    4. 恕我直言是一个非常优雅的解决方案,比Cron Jobs等待以前的乔布斯完成的锁只更糟糕。

答案 1 :(得分:1)

将锁定模式设置为PESSIMISTIC_READ,因为在更改数据之前,您需要第二台服务器知道第一台服务器的更改。

答案 2 :(得分:0)

我可以确认Ricardos的观察。我已经使用H2-Database测试了几种锁定模式,并且所有锁定模式均按预期工作。两种悲观的锁定模式都无法与Oracle数据库结合使用。我没有尝试过乐观锁定,但是令人惊讶的是,有一种锁定模式根本无法与顶级狗配合使用。