Интервьюер: Расскажите о поведении распространения транзакций в Spring.

Java Spring

предисловие

Я считаю, что в разработке все использовали функцию управления транзакциями 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.