Запрет чтения той же записи с Hibernate - PullRequest
0 голосов
/ 19 апреля 2019

Я использую 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 голосов
/ 19 апреля 2019

Вы говорите, что хотите убедиться, что другие транзакции НЕ ПРОЧИТАЮТ объекты запросов.

Для этого вам нужно LockMode.PESSIMISTIC_WRITE. Это не позволяет и READ, и UPDATE. LockMode.PESSIMISTIC_READ не допускает только ОБНОВЛЕНИЯ.

Блокировка с LockModeType.PESSIMISTIC_WRITE может быть получена на экземпляр объекта для принудительной сериализации между попытками транзакций обновить данные объекта.

Блокировка с LockModeType.PESSIMISTIC_WRITE может использоваться при запросе данные и существует высокая вероятность тупика или сбоя обновления среди одновременных обновлений транзакций.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...