Spring Data JPA: транзакция с изоляцией READ_COMMITTED не видит данные, зафиксированные в другой транзакции - PullRequest
0 голосов
/ 29 ноября 2018

Итак, у меня есть очень простое веб-приложение на основе Spring Boot.

База данных содержит одну таблицу user со столбцами id и username и одну запись: (1, 'Joe').

У меня также есть следующие классы:

User - объект, сопоставленный с таблицей user

UserRepository - репозиторий Spring Data JPA для него

UserService + DefaultUserService - сервисный уровень с методами CRUD

UserController - контроллер с двумя методами: get и update

Application - основной класс (с пометкой @EnableTransactionManagement)

Итак, я пытаюсь проверить уровень изоляции транзакции READ_COMMITTED.Я отправляю два одновременных запроса:

  1. К /update, который обновляет пользователя, устанавливает его имя на Jack, затем переводит текущий поток в спящий режим на 5 с и затем фиксирует транзакцию.
  2. К /get, который неоднократно читает одного и того же пользователя 10 раз, делая сон в течение 1 с после каждой попытки.

Проблема в том, что даже после транзакции для (1)было зафиксировано, (2) продолжает возвращать старое значение - Joe.Если после этого я попытаюсь отправить еще один запрос на /get, он возвращает Jack, как и ожидалось, поэтому проблема возникает только в том случае, если транзакция (2) началась до того, как транзакция (1) зафиксировала изменение в БД.

Ниже приведен код для справки.

Служба:

@Service
public class DefaultUserService implements UserService {

    ... //fields & constructor

    @Override
    @SneakyThrows
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public User read(Long id) {
        User user = userRepository.findById(id).get();
        Thread.sleep(1000);
        return user;
    }

    @Override
    @SneakyThrows
    @Transactional
    public User update(Long id, User update) {
        log.info("Entering update method for user {}", id);
        User user = read(id);

        user.setUsername("Jack");
        user = userRepository.save(user);

        log.info("User {} updated, falling asleep for 5s", id);
        Thread.sleep(5000);

        return user;
    }
}

Контроллер:

@RestController
public class UserController {

    ... //fields & constructor

    @RequestMapping("/update")
    public User update() {
        User user = userService.update(1L, new User("Jack"));
        log.info("UPDATED: {}", user);
        return user;
    }

    @RequestMapping("/get")
    public User get() {
        User user = userService.read(1L);
        for (int i = 0; i < 10; i++) {
            log.info("READ: {}", user);
            user = userService.read(1L);
        }
        return user;
    }
}    

Выход журнала:

04:37:52.915 [io-8781-exec-10] READ: ID: 1 :: Joe
04:37:53.151 [nio-8781-exec-1] Entering update method for user 1
04:37:53.919 [io-8781-exec-10] READ: ID: 1 :: Joe
04:37:54.152 [nio-8781-exec-1] User 1 updated, falling asleep for 5s
04:37:54.922 [io-8781-exec-10] READ: ID: 1 :: Joe
04:37:55.926 [io-8781-exec-10] READ: ID: 1 :: Joe
04:37:56.932 [io-8781-exec-10] READ: ID: 1 :: Joe
04:37:57.937 [io-8781-exec-10] READ: ID: 1 :: Joe
04:37:58.943 [io-8781-exec-10] READ: ID: 1 :: Joe
04:37:59.222 [nio-8781-exec-1] UPDATED: ID: 1 :: Jack
04:37:59.947 [io-8781-exec-10] READ: ID: 1 :: Joe
04:38:00.950 [io-8781-exec-10] READ: ID: 1 :: Joe
04:38:01.956 [io-8781-exec-10] READ: ID: 1 :: Joe

Ответы, возвращаемые контроллерами, также различны.Для /update это:

{"id":1,"username":"Jack"}

Для /get:

{"id":1,"username":"Joe"}

Я использую MySQL 5.7.18 и Spring Boot 2.1.0.

Любые идеи о том, что я могу делать неправильно / отсутствует?Заранее спасибо.

1 Ответ

0 голосов
/ 29 ноября 2018

Ваш код работает правильно с Oracle, но с MySQL немного по-другому.Читайте здесь об уровнях изоляции в MySql

https://blog.pythian.com/understanding-mysql-isolation-levels-repeatable-read/

Я полагаю, если вы измените isolation level вашего метода на Read committed, он должен работать, так как по умолчанию MySql имеет Repeatable-read, чтообрабатывается бит по-разному.

   @Transactional(Isolation.READ_COMMITTED)
    public User update(Long id, User update) 
...