Hibernate如何处理LockMode.PESSIMISTIC_WRITE
?它是否以与本机查询中的SELECT FOR UPDATE
相同的方式处理它?
我运行2个实验性事务(T1和T2),它们执行相同的本机SQL,该本机SQL使用SELECT FOR UPDATE
选择一个实体,然后对其进行更新。我在2个线程中运行它们,以确保时间安排如下:
1) T1 SELECT ... FOR UPDATE (based on a condition that becomes false after update)
2) T2 SELECT ... FOR UPDATE (based on a condition that becomes false after update)
3) T2 updates the entity
4) T1 updates the entity
这总是导致我理解和期望的相同流程和结果:
1) T1 reads the single entity based on a condition
2) T2 doesn't select anything
3) T1 updates the entity so that it cannot be selected based on the condition anymore
4) T2 doesn't update anything
该实体的类型为FIELD
,具有4个属性:ID, NAME, TYPE, DESCRIPTION
。仅DESCRIPTION
被更新。
这是运行此类事务的代码:
private void selectForUpdateNativeQueryTransaction(String name, int delayBeforeRead, int delayBeforeCommit) {
Transaction tx = null;
Session session = null;
try {
session = factory.openSession();
tx = session.beginTransaction();
System.out.println(name + " BEGIN");
try {
TimeUnit.SECONDS.sleep(delayBeforeRead);
} catch (InterruptedException e) {
e.printStackTrace();
}
SQLQuery sqlQuery = session.createSQLQuery("SELECT * FROM FIELD WHERE DESCRIPTION=?1 FOR UPDATE");
sqlQuery.setParameter("1", DESC);
List results = sqlQuery.list();
System.out.println(name + " SELECT EXECUTED");
results.forEach(r -> {
Object[] row = (Object[]) r;
for (int i = 0; i < row.length; i++) {
System.out.println(name + " : FIELD READ [" + row[i] + "]");
}
});
try {
TimeUnit.SECONDS.sleep(delayBeforeCommit);
} catch (InterruptedException e) {
e.printStackTrace();
}
Query query = session.createSQLQuery("UPDATE FIELD SET DESCRIPTION=?1 WHERE DESCRIPTION=?2");
query.setParameter("1", name);
query.setParameter("2", DESC);
int result = query.executeUpdate();
System.out.println(name + " UPDATED ROWS: " + result);
tx.commit();
} catch (Exception e) {
fail();
if (tx != null) {
tx.rollback();
}
} finally {
session.close();
}
System.out.println(name + " COMMITTED");
}
下面是在不同线程中运行两个事务的代码:
@Test
public void firstReadsTheOtherRejected() {
ExecutorService es = Executors.newFixedThreadPool(3);
Runnable selectForUpdateNativeQueryTransaction1 = () -> {
selectForUpdateNativeQueryTransaction("T1", 1, 8);
};
Runnable selectForUpdateNativeQueryTransaction2 = () -> {
selectForUpdateNativeQueryTransaction("T2", 3, 2);
};
Runnable selectForUpdateHqlQueryTransaction1 = () -> {
selectForUpdateHqlQueryTransaction("T1", 1, 8);
};
Runnable selectForUpdateHqlQueryTransaction2 = () -> {
selectForUpdateHqlQueryTransaction("T2", 3, 2);
};
// es.execute(selectForUpdateHqlQueryTransaction1);
// es.execute(selectForUpdateHqlQueryTransaction2);
es.execute(selectForUpdateNativeQueryTransaction1);
es.execute(selectForUpdateNativeQueryTransaction2);
es.shutdown();
try {
es.awaitTermination(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
这是输出:
T2 BEGIN
T1 BEGIN
Hibernate: SELECT * FROM FIELD WHERE DESCRIPTION=? FOR UPDATE
T1 SELECT EXECUTED
T1 : FIELD READ [16]
T1 : FIELD READ [Test field]
T1 : FIELD READ [Test type]
T1 : FIELD READ [This is a field for testing]
Hibernate: SELECT * FROM FIELD WHERE DESCRIPTION=? FOR UPDATE
Hibernate: UPDATE FIELD SET DESCRIPTION=? WHERE DESCRIPTION=?
T1 UPDATED ROWS: 1
T1 COMMITTED
T2 SELECT EXECUTED
Hibernate: UPDATE FIELD SET DESCRIPTION=? WHERE DESCRIPTION=?
T2 UPDATED ROWS: 0
T2 COMMITTED
注意:T2 UPDATED ROWS: 0
但是,如果我尝试使用Hql达到相同的效果,则会给我一些奇怪的输出。 这是交易的代码:
private void selectForUpdateHqlQueryTransaction(String name, int delayBeforeRead, int delayBeforeCommit) {
Transaction tx = null;
Session session = null;
try {
session = factory.withOptions().interceptor(new HibernateInterceptor()).openSession();
tx = session.beginTransaction();
System.out.println(name + " BEGIN");
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);
List fields = query.list();
System.out.println(name + " SELECT EXECUTED");
fields.forEach(obj -> {
Field field = (Field) obj;
System.out.println(name + " : FIELD READ [" + field.getId() + "]");
System.out.println(name + " : FIELD READ [" + field.getName() + "]");
System.out.println(name + " : FIELD READ [" + field.getType() + "]");
System.out.println(name + " : FIELD READ [" + field.getDescription() + "]");
});
try {
TimeUnit.SECONDS.sleep(delayBeforeCommit);
} catch (InterruptedException e) {
e.printStackTrace();
}
Session finalSession = session;
fields.forEach(obj -> {
Field field = (Field) obj;
field.setDescription(name);
finalSession.update(field);
});
System.out.println(name + " UPDATED ROWS: " + fields.size());
tx.commit();
} catch (Exception e) {
fail();
if (tx != null) {
tx.rollback();
}
} finally {
session.close();
}
System.out.println(name + " COMMITTED");
}
输出:
T2 BEGIN
T1 BEGIN
апр 22, 2019 4:29:37 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]
апр 22, 2019 4:29:37 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 SELECT EXECUTED
T1 : FIELD READ [16]
T1 : FIELD READ [Test field]
T1 : FIELD READ [Test type]
T1 : FIELD READ [This is a field for testing]
апр 22, 2019 4:29:39 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]
апр 22, 2019 4:29:39 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 UPDATED ROWS: 1
previousState in onFlushDirty(): []
previousState in onFlushDirty(): This is a field for testing
previousState in onFlushDirty(): Test field
previousState in onFlushDirty(): []
previousState in onFlushDirty(): Test type
currentState in onFlushDirty(): []
currentState in onFlushDirty(): T1
currentState in onFlushDirty(): Test field
currentState in onFlushDirty(): []
currentState in onFlushDirty(): Test type
Hibernate: update FIELD set DESCRIPTION=?, NAME=?, TYPE=? where ID=?
T2 SELECT EXECUTED
T1 COMMITTED
T2 : FIELD READ [16]
T2 : FIELD READ [Test field]
T2 : FIELD READ [Test type]
T2 : FIELD READ [This is a field for testing]
T2 UPDATED ROWS: 1
previousState in onFlushDirty(): []
previousState in onFlushDirty(): This is a field for testing
previousState in onFlushDirty(): Test field
previousState in onFlushDirty(): []
previousState in onFlushDirty(): Test type
currentState in onFlushDirty(): []
currentState in onFlushDirty(): T2
currentState in onFlushDirty(): Test field
currentState in onFlushDirty(): []
currentState in onFlushDirty(): Test type
Hibernate: update FIELD set DESCRIPTION=?, NAME=?, TYPE=? where ID=?
T2 COMMITTED
您可能已经注意到,我使用Hibernate Interceptor来拦截SQL操作。 Interceptor.onFlushDirty()
在SQL UPDATE
之前被调用。因此,我不清楚为什么我会在输出中看到两个事务都更新了值:
...
currentState in onFlushDirty(): T1
...
currentState in onFlushDirty(): T2
...
第一个问题是: 真的只有1次更新,还是两个交易都更新了价值?当我更改了负责UPDATE的代码时,必须只有1个更新,以便它不会覆盖旧值,而是将事务名称附加到该值上。在两个事务完成之后,该列的末尾不包含“ ... T1:T2”之类的内容。很好。
但是下一个问题是: 为什么HQL版本未选择本机SQL版本却选择了旧实体?是因为缓存吗?我想确切地知道哪个交易“成功”了,哪个没有。