Аннотация транзакции Spring @Transactional может изначально гарантировать атомарность.Если в транзакции есть ошибка, можно гарантировать откат всей транзакции, но добавление try catch или вложенности транзакции может привести к сбою отката транзакции. Испытайте волну.
Подготовить
Создайте две таблицы и смоделируйте две операции с данными
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`age` smallint(3) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
CREATE TABLE `role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`role_name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
контрольная работа
По принципу перестановки и комбинирования проводим четыре вида тестов: 1) без попытки отлова, без вложенности, 2 с попыткой отлова, без вложения, 3 без попытки отлова, с вложением, 4, оба.
Самый простой тест
Если мы просто @Transactional, можно ли нормально откатить транзакцию?
@GetMapping("/saveNormal0")
@Transactional
public void saveNormal0() throws Exception {
int age = random.nextInt(100);
User user = new User().setAge(age).setName("name:"+age);
userService.save(user);
throw new RuntimeException();
}
Если в транзакции сообщается об ошибке RuntimeException, транзакцию можно откатить.
@GetMapping("/saveNormal0")
@Transactional
public void saveNormal0() throws Exception {
int age = random.nextInt(100);
User user = new User().setAge(age).setName("name:"+age);
userService.save(user);
throw new Exception();
}
Если в транзакции сообщается об ошибке Exception (не RuntimeException error), транзакцию нельзя откатить.
@GetMapping("/saveNormal0")
@Transactional( rollbackFor = Exception.class)
public void saveNormal0() throws Exception {
int age = random.nextInt(100);
User user = new User().setAge(age).setName("name:"+age);
userService.save(user);
throw new Exception();
}
Если это ошибка исключения (не RuntimeException), параметр rollbackFor = Exception.class также можно откатить.
Вывод 1: Поскольку @Transactional может гарантировать откат ошибок RuntimeException, если вы хотите обеспечить откат ошибок, отличных от RuntimeException, вам необходимо добавить параметр rollbackFor = Exception.class.
попробуйте поймать влияет
После тестирования в различных ситуациях блогером установлено, что try catch никак не влияет на сам откат, и вывод остается прежним. try catch только для того, может ли исключение быть @Transactionalвосприятиеиметь влияние. Если ошибка доведена до точки, где аспект может ее воспринять, она может сработать.
@GetMapping("/saveTryCatch")
@Transactional( rollbackFor = Exception.class)
public void saveTryCatch() throws Exception{
try{
int age = random.nextInt(100);
User user = new User().setAge(age).setName("name:"+age);
userService.save(user);
throw new Exception();
}catch (Exception e){
throw e;
}
}
Например, приведенный выше код откатывается.
@GetMapping("/saveTryCatch")
@Transactional( rollbackFor = Exception.class)
public void saveTryCatch() throws Exception{
try{
int age = random.nextInt(100);
User user = new User().setAge(age).setName("name:"+age);
userService.save(user);
throw new Exception();
}catch (Exception e){
}
}
Однако, если ошибка в catch не продолжает вылетать в онлайн, аспект не может воспринять ошибку и не может ее обработать, то транзакцию нельзя откатить.
Вывод 2: try catch предназначен только для того, может ли исключение быть @Transactionalвосприятиеиметь влияние. Если ошибка доведена до точки, где аспект может ее воспринять, она может сработать.
Влияние вложенности транзакций
Во-первых, после эксперимента вывод 1 все же установлен, то есть, когда rollbackFor = Exception.class не добавляется, независимо от того, сообщается RuntimeException внутри или снаружи, оно будет откатываться, независимо от того, Об ошибке RuntimeException сообщается внутренне или внешне, она не будет отменена. Если вы добавите rollbackFor = Exception.class, независимо от того, как будет сообщаться об ошибке внутри или снаружи, она будет откатана. Эти коды не даются. Затем попробуйте следующие два случая:
@GetMapping("/out")
@Transactional( rollbackFor = Exception.class)
public void out() throws Exception{
innerService.inner();
int age = random.nextInt(100);
User user = new User().setAge(age).setName("name:" + age);
userService.save(user);
throw new Exception();
}
@Transactional
public void inner() throws Exception{
Role role = new Role();
role.setRoleName("roleName:"+new Random().nextInt(100));
roleService.save(role);
// throw new Exception();
}
В случае 1, если во внешнюю транзакцию добавляется rollbackFor = Exception.class, а внутренняя транзакция не добавляется, тест можно откатить при сообщении об ошибках внутри и вне теста (чтобы упростить объем кода, дан только код, сообщающий об ошибке снаружи). Потому что в любом случае ошибка выбрасывается во внешнюю транзакцию для обработки, а внешняя добавляет rollbackFor = Exception.class, который имеет возможность обрабатывать не-RuntimeException ошибки, поэтому транзакцию можно нормально откатить.
Рассмотрим второй случай, транзакция внутри добавляется с rollbackFor=Exception.class, а снаружи не добавляется, и сообщается об ошибке снаружи.
@GetMapping("/out")
@Transactional
public void out() throws Exception{
innerService.inner();
int age = random.nextInt(100);
User user = new User().setAge(age).setName("name:" + age);
userService.save(user);
throw new Exception();
}
@Transactional( rollbackFor = Exception.class)
public void inner() throws Exception{
Role role = new Role();
role.setRoleName("roleName:"+new Random().nextInt(100));
roleService.save(role);
}
Транзакции не могут быть отброшены.Это вопрос, который у нас есть.Транзакции внутри, очевидно, имеют сильные возможности обработки.Почему откат не удался с внешним миром?Не волнуйтесь, давайте поговорим об этом.
Затем попробуйте ошибку внутри:
@GetMapping("/out")
@Transactional
public void out() throws Exception{
innerService.inner();
int age = random.nextInt(100);
User user = new User().setAge(age).setName("name:" + age);
userService.save(user);
}
@Transactional( rollbackFor = Exception.class)
public void inner() throws Exception{
Role role = new Role();
role.setRoleName("roleName:"+new Random().nextInt(100));
roleService.save(role);
throw new Exception();
}
Эй, на этот раз был выполнен обычный откат. Боже мой, снаружи на этот раз нет вычислительной мощности, зачем принимать выброшенную внутрь ошибку и откатывать назад! ! ! Кажется, что внутренние и внешние дела всегда идут рука об руку, верно? Оказывается, у @Transactional тоже есть параметр.Глядя на исходный код, эта аннотация имеет значение по умолчанию:
Propagation propagation() default Propagation.REQUIRED;
REQUIRED означает, что когда транзакция вложена, если обнаруживается, что транзакция уже существует, она присоединится к транзакции, а не создаст новую транзакцию, поэтому двух транзакций вообще не существует, всегда есть только одна! Что касается других значений этого параметра, то в этой статье тест не проводится. Возвращаясь к заданному выше вопросу, когда снаружи сообщается об ошибке, транзакция в это время проверяется, а параметр rollbackFor=Exception.class не добавляется, то есть нет возможности обрабатывать не-RuntimeException, поэтому после код закончен, похоже "две транзакции" не удалось откатить. . Когда сообщается об ошибке, транзакция была добавлена с возможностью обработки исключения RuntimeException, поэтому откат выполняется успешно после завершения кода.
Вывод 3: Из-за атрибута REQUIRED "две транзакции" на самом деле являются одной транзакцией. Возможность обработки зависит от времени сообщения об ошибке и от того, была ли добавлена возможность обработки исключения, отличного от RuntimeException.
Комбинированный эффект от try catch и вложения транзакций
При условии, что выводы 1, 2 и 3 установлены, задача исследования общего влияния значительно упрощается, так как ситуаций слишком много, отображение слишком большого количества кода не выполняется.
в заключении
Вывод 1: Поскольку @Transactional может гарантировать откат ошибок RuntimeException, если вы хотите обеспечить откат ошибок, отличных от RuntimeException, вам необходимо добавить параметр rollbackFor = Exception.class. Вывод 2: try catch предназначен только для того, может ли исключение быть @Transactionalвосприятиеиметь влияние. Если ошибка доведена до точки, где аспект может ее воспринять, она может сработать. Вывод 3: Из-за атрибута REQUIRED "две транзакции" на самом деле являются одной транзакцией. Возможность обработки зависит от времени сообщения об ошибке и от того, была ли добавлена возможность обработки исключения, отличного от RuntimeException.