我使用Hibernate 5和Oracle 12。 通过以下查询,我想从一组实体中随机选择一个实体:
Query query = getSession().createQuery("SELECT e FROM Entity e ... <CONDITIONS> ... AND ROWNUM = 1");
Optional<Entity> entity = query.list().stream().findAny();
// Change the entity in some way. The changes will also make sure that the entity won't appear in the next query run based on <CONDITIONS>
...
这有效,但前提是所有执行代码的事务均按顺序运行。因此,我还想确保已经读取的实体不会在另一个事务中读取。 我尝试了锁定:
Query query = getSession().createQuery("SELECT e FROM Entity e ... <CONDITIONS> ... AND ROWNUM = 1")
.setLockMode("this", LockMode.PESSIMISTIC_READ);
但是,似乎Hibernate将此构造转换为SELECT ... FOR UPDATE
,这不会阻止其他事务读取实体,等到其他使用它的事务提交后再对实体应用自己的更改。
是否可以在实体上设置某种锁定,以确保它在查询的另一笔交易中消失?
我编写了一些实验代码来了解Hibernate中锁定的工作方式。它模拟两个事务,这些事务的关键步骤(选择和提交)可以通过调整transaction()
方法的参数以不同的顺序执行。这次使用Field
代替了Entity
,但这并不重要。每个事务读取相同的Field
,更新其description
属性并提交。
private static final LockMode lockMode = LockMode.PESSIMISTIC_WRITE;
enum Order {T1_READS_EARLIER_COMMITS_LATER, T2_READS_EARLIER_COMMITS_LATER};
@Test
public void firstReadsTheOtherRejected() {
ExecutorService es = Executors.newFixedThreadPool(3);
// It looks like the transaction that commits first is the only transaction that can make changes.
// The changes of the other one will be ignored.
final Order order = Order.T1_READS_EARLIER_COMMITS_LATER;
// final Order order = Order.T2_READS_EARLIER_COMMITS_LATER;
es.execute(() -> {
switch (order) {
case T1_READS_EARLIER_COMMITS_LATER:
transaction("T1", 1, 8);
break;
case T2_READS_EARLIER_COMMITS_LATER:
transaction("T1", 4, 1);
break;
}
});
es.execute(() -> {
switch (order) {
case T1_READS_EARLIER_COMMITS_LATER:
transaction("T2", 4, 1);
break;
case T2_READS_EARLIER_COMMITS_LATER:
transaction("T2", 1, 8);
break;
}
});
es.shutdown();
try {
es.awaitTermination(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void transaction(String name, int delayBeforeRead, int delayBeforeCommit) {
Transaction tx = null;
Session session = null;
try {
session = factory.openSession();
tx = session.beginTransaction();
try {
TimeUnit.SECONDS.sleep(delayBeforeRead);
} catch (InterruptedException e) {
e.printStackTrace();
}
Query query = session.createQuery("SELECT f FROM Field f WHERE f.description=?1").setLockMode("this", lockMode);
query.setString("1", DESC);
Field field = (Field) query.uniqueResult();
String description1 = field.getDescription();
System.out.println(name + " : FIELD READ " + description1);
try {
TimeUnit.SECONDS.sleep(delayBeforeCommit);
} catch (InterruptedException e) {
e.printStackTrace();
}
field.setDescription(name);
session.update(field);
System.out.println(name + " : FIELD UPDATED");
tx.commit();
} catch (Exception e) {
fail();
if (tx != null) {
tx.rollback();
}
} finally {
session.close();
}
System.out.println(name + " : COMMITTED");
}
和输出:
T1 : FIELD READ This is a field for testing
апр 19, 2019 5:28:01 PM org.hibernate.loader.Loader determineFollowOnLockMode
WARN: HHH000445: Alias-specific lock modes requested, which is not currently supported with follow-on locking; all acquired locks will be [PESSIMISTIC_WRITE]
апр 19, 2019 5:28:01 PM org.hibernate.loader.Loader shouldUseFollowOnLocking
WARN: 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
Hibernate: select field0_.ID as ID1_9_, field0_.DESCRIPTION as DESCRIPTION2_9_, field0_.NAME as NAME3_9_, field0_.TYPE as TYPE4_9_ from FIELD field0_ where field0_.DESCRIPTION=?
Hibernate: select ID from FIELD where ID =? for update
T1 : FIELD UPDATED
Hibernate: update FIELD set DESCRIPTION=?, NAME=?, TYPE=? where ID=?
T2 : FIELD READ This is a field for testing
T1 : COMMITTED
апр 19, 2019 5:28:07 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
T2 : FIELD UPDATED
Hibernate: update FIELD set DESCRIPTION=?, NAME=?, TYPE=? where ID=?
INFO: HHH000030: Cleaning up connection pool [jdbc:oracle:thin:@localhost:1521:oracle]
T2 : COMMITTED
Process finished with exit code 0
执行后,列description
包含T2
。看来pessimistic_write
模式有效。首先写的交易-赢了。这就是T2
。但是T1发生了什么? T1 : COMMITTED
也出现在输出中。只要T1
不做任何更改,对我来说都是可以接受的,但是我需要一个指示T1
失败的指示器,以便我可以重试读取/选择。
我错了。我多次运行该代码,结果却不同。有时列说明包含T1,有时包含T2。
答案 0 :(得分:0)
您说您想确保其他事务不会读取查询实体。
为此,您需要main:
logout:
path: app_logout
anonymous: true
provider: backend_users
guard:
#....
。这不允许同时进行READ和UPDATE。 LockMode.PESSIMISTIC_WRITE
不允许仅进行更新。
可以在计算机上获取具有LockModeType.PESSIMISTIC_WRITE的锁。 实体实例可在尝试进行的交易之间强制序列化 更新实体数据。
查询时可以使用具有LockModeType.PESSIMISTIC_WRITE的锁 数据,并且极有可能出现死锁或更新失败 在并发更新交易中。