Итак, у меня есть очень простое веб-приложение на основе Spring Boot.
База данных содержит одну таблицу user
со столбцами id
и username
и одну запись: (1, 'Joe')
.
У меня также есть следующие классы:
User
- объект, сопоставленный с таблицей user
UserRepository
- репозиторий Spring Data JPA для него
UserService
+ DefaultUserService
- сервисный уровень с методами CRUD
UserController
- контроллер с двумя методами: get
и update
Application
- основной класс (с пометкой @EnableTransactionManagement
)
Итак, я пытаюсь проверить уровень изоляции транзакции READ_COMMITTED
.Я отправляю два одновременных запроса:
- К
/update
, который обновляет пользователя, устанавливает его имя на Jack
, затем переводит текущий поток в спящий режим на 5 с и затем фиксирует транзакцию. - К
/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.
Любые идеи о том, что я могу делать неправильно / отсутствует?Заранее спасибо.