LockMode.PESSIMISTIC_WRITE在Hibernate中的行为与本机查询中的SELECT FOR UPDATE有所不同

时间:2019-04-22 13:46:36

标签: oracle hibernate pessimistic-locking

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版本却选择了旧实体?是因为缓存吗?我想确切地知道哪个交易“成功”了,哪个没有。

0 个答案:

没有答案