предисловие
Я считаю, что в разработке все использовали функцию управления транзакциями Spring. Итак, вы когда-нибудь узнавали о поведении Spring при распространении транзакций?
В Spring существует 7 типов поведения распространения транзакций. Поведение распространения транзакций — это метод управления транзакциями, предоставляемый инфраструктурой Spring, который не предоставляется базой данных. Я не знаю, слышали ли вы утверждение «Не вкладывать методы транзакций в методы транзакций службы, чтобы было отправлено несколько транзакций», на самом деле это неверно. Поняв поведение распространения транзакций, я думаю, вы поймете!
исходное заявление
Эта статья была впервые опубликована под номером заголовка [Happyjava]. Адрес Happy’s Nuggets:Талант /user/398428…, личный блог Хэппи :(blog.happyjava.cn)[blog.happyjava.cn]. Перепечатки приветствуются, но это заявление должно быть сохранено.
Семь способов распространения транзакций в Spring
Поведение транзакции при распространении, значение по умолчанию — Propagation.REQUIRED. Дополнительные способы распространения транзакций можно указать вручную следующим образом:
- Propagation.REQUIRED
Если транзакция в настоящее время существует, присоединиться к транзакции, если текущей транзакции нет, создать новую транзакцию.
- Propagation.SUPPORTS
Если в данный момент есть транзакция, присоединитесь к ней; если текущей транзакции нет, продолжите работу в нетранзакционном режиме.
- Propagation.MANDATORY
Если есть текущая транзакция, присоединиться к транзакции; если текущей транзакции нет, сгенерировать исключение.
- Propagation.REQUIRES_NEW
Воссоздайте новую транзакцию, приостановив текущую транзакцию, если она существует.
- Propagation.NOT_SUPPORTED
Выполняется в нетранзакционном режиме, приостанавливая текущую транзакцию, если есть текущая транзакция.
- Propagation.NEVER
Работает нетранзакционным образом, вызывая исключение, если в данный момент есть транзакция.
- Propagation.NESTED
Если нет, создайте новую транзакцию; если есть, вложите другие транзакции в текущую транзакцию.
Готов к работе
Таблица базы данных:
CREATE TABLE `t_user` (
`id` int(11) NOT NULL,
`password` varchar(255) DEFAULT NULL,
`username` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Проект SpringBoot, который интегрирует Spring Data JPA, поэтому я не буду здесь много говорить.
Требуется (распространение транзакций по умолчанию)
Поведение распространения транзакции по умолчанию распространяется. Рассматривается, то есть: если существует текущая транзакция, присоединиться к транзакции, если нет текущей транзакции, создайте новую транзакцию.
Далее, давайте проверим проблему «не зацикливать методы вложенных транзакций», упомянутую ранее:
Теперь есть две службы, а именно:
UserService.java
@Service
public class UserService {
@Autowired
private UserRepo userRepo;
@Transactional(propagation = Propagation.REQUIRED)
public void insert() {
UserEntity user = new UserEntity();
user.setUsername("happyjava");
user.setPassword("123456");
userRepo.save(user);
}
}
Здесь все очень просто, просто метод вставки, вставляющий пользователя.
UserService2.java
@Service
public class UserService2 {
@Autowired
private UserService userService;
@Transactional
public void inserBatch() {
for (int i = 0; i < 10; i++) {
if (i == 9) {
throw new RuntimeException();
}
userService.insert();
}
}
}
Внедрите UserService и вызовите метод параметра десять раз в цикле. И в десятый раз выбрасывается исключение. Вызовите метод insertBatch, чтобы увидеть результат:
@Test
public void insertBatchTest() {
userService2.inserBatch();
}
Результат выглядит следующим образом:
Нет в записи базы данных:
Это также подтверждает концепцию «если транзакция в настоящее время существует, присоединиться к транзакции». Если вы столкнетесь с кем-то, кто говорит, что в будущем не следует зацикливать вложенные транзакции, вы можете попросить его вернуться и хорошенько взглянуть на поведение распространения транзакций в Spring.
SUPPORTS
Если в данный момент есть транзакция, присоединитесь к ней; если текущей транзакции нет, продолжите работу в нетранзакционном режиме. Другими словами, поддерживает ли режим транзакции, зависит от того, поддерживает ли вызывающий его метод. Код теста выглядит следующим образом:
UserService
@Transactional(propagation = Propagation.SUPPORTS)
public void insert() {
UserEntity user = new UserEntity();
user.setUsername("happyjava");
user.setPassword("123456");
userRepo.save(user);
throw new RuntimeException();
}
UserService2
public void insertWithoutTx() {
userService.insert();
}
Вызванный метод не запускает транзакцию, а результат операции:
Операция сообщила об ошибке, но данные не откатились. Объясняет, что метод вставки не выполняется в транзакции.
MANDATORY
Если есть текущая транзакция, присоединиться к транзакции; если текущей транзакции нет, сгенерировать исключение. Обязательный на китайском языке означает обязательный, указывая на то, что измененный метод должен вызываться в транзакции, иначе будет выдано исключение.
UserService.java
@Transactional(propagation = Propagation.MANDATORY)
public void insert() {
UserEntity user = new UserEntity();
user.setUsername("happyjava");
user.setPassword("123456");
userRepo.save(user);
}
UserService2.java
public void insertWithoutTx() {
userService.insert();
}
перечислить:
@Test
public void insertWithoutTxTest() {
userService2.insertWithoutTx();
}
результат операции:
Возникло исключение, указывающее на отсутствие существующей транзакции.
REQUIRES_NEW
Это может быть запутанным для понимания.Официальное объяснение выглядит следующим образом:
Create a new transaction, and suspend the current transaction if one exists.
Общая идея такова: воссоздайте новую транзакцию, и если есть текущая транзакция, задержите текущую транзакцию. Эту задержку или зависание может быть трудно понять. Давайте проанализируем это на примере:
UserService.java
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insert() {
UserEntity user = new UserEntity();
user.setUsername("happyjava");
user.setPassword("123456");
userRepo.save(user);
}
Поведение распространения этого метода вставки изменено на REQUIRES_NEW.
UserService2.java
@Transactional
public void inserBatch() {
UserEntity user = new UserEntity();
user.setUsername("初次调用");
user.setPassword("123456");
userRepo.save(user);
for (int i = 0; i < 10; i++) {
if (i == 9) {
throw new RuntimeException();
}
userService.insert();
}
}
У insertBatch есть транзакция, а затем у метода вставки, который циклически вызывается позже, тоже есть своя транзакция. По определению транзакции insertBatch откладываются. Конкретная производительность такова: транзакции следующих 10 циклов будут отправлять свои собственные транзакции после каждого цикла, а транзакции insertBatch будут отправляться после завершения метода цикла. Однако, поскольку 10-й цикл выкинет исключение, транзакция insertBatch будет откатана, то есть в базе данных не будет записи: «первоначальный вызов»:
Тестовый код:
@Test
public void insertBatchTest() {
userService2.inserBatch();
}
Результаты:
Эта ситуация соответствует поговорке «Не зацикливайте вложенные методы транзакций» в начале.Конечно, требуется ли вложенность циклов, зависит от бизнес-логики.
NOT_SUPPORTED
Execute non-transactionally, suspend the current transaction if one exists.
Выполняется в нетранзакционном режиме, приостанавливая текущую транзакцию, если есть текущая транзакция. Этот метод похож на REQUIRES_NEW, но сам метод, модифицированный NOT_SUPPORTED, не является транзакционным. Здесь нет демонстрации кода.
NEVER
Работает нетранзакционным образом, вызывая исключение, если в данный момент есть транзакция.
@Transactional(propagation = Propagation.NEVER)
public void insert() {
UserEntity user = new UserEntity();
user.setUsername("happyjava");
user.setPassword("123456");
userRepo.save(user);
}
@Transactional
public void insertWithTx() {
userService.insert();
}
Результаты:
NESTED
Если транзакции нет, создайте новую транзакцию, если есть, вложите другие транзакции в текущую транзакцию.
Это также поведение, которое сложнее понять. Мы анализируем шаг за шагом.
Периферийные методы не затрагиваются: эта ситуация такая же, как и требуется, создаст новую транзакцию.
Если во внешнем методе есть транзакция: в этом случае транзакция вложена. Так называемая вложенная транзакция означает, что периферийная транзакция откатывается, встроенная транзакция должна быть отброшена, а встроенная транзакция может быть отброшена одна, не затрагивая основную периферийную транзакцию и другие подтранзакции.
Из-за того, что мой демонстрационный код использует Spring Data JPA, при использовании вложенных транзакций будет предложено:
org.springframework.transaction.NestedTransactionNotSupportedException: JpaDialect does not support savepoints - check your JPA provider's capabilities
После поиска кажется, что hibernate не поддерживает этот метод распространения транзакций. Поэтому я не буду делать демонстрацию здесь.
Суммировать
Поведение распространения транзакций, которое может быть не особо замечено в разработке (чаще мы можем просто использовать метод по умолчанию), но его все же нужно понять. Я надеюсь, что эта статья поможет вам понять 7 способов распространения транзакций в Spring.