防止使用Hibernate读取相同的记录

时间:2019-04-19 12:53:44

标签: java oracle hibernate jpa

我使用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。

1 个答案:

答案 0 :(得分:0)

您说您想确保其他事务不会读取查询实体。

为此,您需要main: logout: path: app_logout anonymous: true provider: backend_users guard: #.... 。这不允许同时进行READ和UPDATE。 LockMode.PESSIMISTIC_WRITE不允许仅进行更新。

  

可以在计算机上获取具有LockModeType.PESSIMISTIC_WRITE的锁。   实体实例可在尝试进行的交易之间强制序列化   更新实体数据。

     

查询时可以使用具有LockModeType.PESSIMISTIC_WRITE的锁   数据,并且极有可能出现死锁或更新失败   在并发更新交易中。