Spring JPA: как убедиться, что новая транзакция начинается с зафиксированного состояния ранее зафиксированной транзакции? - PullRequest
0 голосов
/ 18 февраля 2020

В загрузочном проекте Spring с данными JPA с Hibernate в базе данных PostgreSQL несколько задач выполняются одновременно. Есть пул TaskExecutor и пул соединений с базой данных.

Иногда для выполнения этих задач требуются одни и те же объекты (обновление: объект означает объекты, хранящиеся в базе данных). В попытке убедиться, что задачи не конфликтуют (обновление: «не пытайтесь одновременно получать доступ к одним и тем же записям»), была создана служба блокировки. Задача получает блокировку нужных ей объектов и снимает блокировку только после ее завершения, после чего следующая задача может получить блокировку и начать свою работу.

На практике это не так. т работает. Один конкретный случай удаления записи в задаче A и ее видимости во время части задачи B продолжает появляться. Фактическим исключением является ограничение внешнего ключа, которое не выполняется: задача B сначала выбирает (удаленный) объект как объект, для которого должна быть создана связь (поэтому задача B по-прежнему видит удаленный объект в этой точке!), Но затем при создании отношения в задаче B не удается, потому что удаленный объект больше не присутствует.

После консультации с коллегой возникла идея, что очистка хранилища - это не то же самое, что фиксация. Следовательно, задача A разблокируется, когда ее лог c выполнен и изменения сброшены, но измененные данные еще не были зафиксированы в базе данных. В то же время задача B получает блокировку и начинает читать данные, и лишь немного позже происходит фиксация для задачи A.

Чтобы убедиться, что блокировка задачи A снята только после изменения в его базе данных были зафиксированы, я попробовал этот код:

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
    @Override
    public void afterCompletion(int status) {

        logDebugMessage("after completion method called");

        // Release the locks
        if(lockObtained) {
            logDebugMessage("releasing locks");
            lockService.releaseLocks(taskHolder.getId());
            logDebugMessage("done releasing locks");
        }
        else
            logDebugMessage("no locks to release");
    }
});

Это само по себе не заставило проблему исчезнуть. Следующее волнение было в том, что следующая задача, задача B, уже имеет открытую транзакцию, пока она ожидает блокировки. Когда он получает блокировку, он читает, используя эту уже открытую транзакцию, а затем ? По какой-то причине читает данные перед фиксацией. Я признаю, что это не имеет особого смысла, но некоторое отчаяние начинает наступать. В любом случае, с этой дополнительной идеей, каждая задача теперь выполняется так:

@Override
public void run() {

    try{
        // Start the progress
        taskStartedDateTime = LocalDateTime.now();
        logDebugMessage("started");

        // Let the task determine which objects need to be locked
        List<LockableObjectId> objectsToLock = getObjectsToLock();

        // Try to obtain a lock
        lockObtained = locksObtained(objectsToLock);
        if(lockObtained) {

            // Do the actual task in a new transaction
            TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
            transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus status) {

                    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                        @Override
                        public void afterCompletion(int status) {

                            logDebugMessage("after completion method called");

                            // Release the locks
                            if(lockObtained) {
                                logDebugMessage("releasing locks");
                                lockService.releaseLocks(taskHolder.getId());
                                logDebugMessage("done releasing locks");
                            }
                            else
                                logDebugMessage("no locks to release");
                        }
                    });

                    try{

                        // Run the actual task
                        runTask();

Но это все еще не решает проблема. Возможно ли, что фиксация в базе данных была сделана со стороны Java, и задача B считывает базу данных до того, как фиксация выполняется в самой базе данных? Метод afterCompletion вызывается после того, как Java отправил коммит, но до того, как база данных фактически выполнила его? Если да, есть ли способ получить подтверждение базы данных о том, что фиксация действительно была выполнена?

Или мы здесь совершенно не на том пути?

...