предисловие
Spring определяет семь типов поведения распространения транзакций в интерфейсе TransactionDefinition. Поведение распространения транзакций — это уникальная функция улучшения транзакций в среде Spring, и она не относится к фактическому поведению транзакции в базе данных поставщика. Spring предоставляет нам мощный набор инструментов, а использование строк распространения транзакций может обеспечить множество удобств для нашей разработки. Но у людей много недопонимания о нем.Вы наверняка слышали слух, что «транзакции сервисного метода лучше не вкладывать». Чтобы правильно использовать инструмент, сначала нужно понять инструмент. В этой статье подробно представлены семь способов распространения транзакций, а содержание в основном представлено в виде примеров кода.
Базовые концепты
1. Что такое поведение распространения транзакций?
Поведение распространения транзакций используется для описания распространения транзакций, когда метод, модифицированный одним поведением распространения транзакций, вкладывается в другой метод.
Объясните псевдокодом:
public void methodA(){
methodB();
//doSomething
}
@Transaction(Propagation=XXX)
public void methodB(){
//doSomething
}
в кодеmethodA()
вложенный методmethodB()
метод,methodB()
Поведение распространения транзакций@Transaction(Propagation=XXX)
Настройка решает. Здесь следует отметить, чтоmethodA()
Транзакция не открывается, и метод модификации поведения распространения определенной транзакции не обязательно должен вызываться в периферийном методе открытой транзакции.
2. Семь способов распространения транзакций в Spring
Тип поведения распространения транзакции | инструкция |
---|---|
PROPAGATION_REQUIRED | Если текущей транзакции нет, создайте новую транзакцию, если транзакция уже есть, присоединитесь к транзакции. Это самый распространенный выбор. |
PROPAGATION_SUPPORTS | Поддерживать текущую транзакцию, если текущей транзакции нет, она будет выполнена в нетранзакционном режиме. |
PROPAGATION_MANDATORY | Используйте текущую транзакцию или сгенерируйте исключение, если текущей транзакции нет. |
PROPAGATION_REQUIRES_NEW | Создать новую транзакцию.Если есть текущая транзакция, приостановить текущую транзакцию. |
PROPAGATION_NOT_SUPPORTED | Выполнить операцию нетранзакционным способом, приостановив текущую транзакцию, если есть текущая транзакция. |
PROPAGATION_NEVER | Выполняется без транзакций и выдает исключение, если транзакция уже существует. |
PROPAGATION_NESTED | Если транзакция уже существует, выполните ее во вложенной транзакции. Если текущих транзакций нет, сделайте что-то похожее на PROPAGATION_REQUIRED. |
Определение очень простое и легкое для понимания.Давайте перейдем к части тестирования кода, чтобы проверить правильность нашего понимания.
проверка кода
Код в этой статье представлен в двух слоях в традиционной трехслойной структуре, а именно слоях Service и Dao. Spring отвечает за внедрение зависимостей и управление аннотированными транзакциями. Уровень DAO реализован Mybatis. Вы также можете использовать любой метод вам нравится, например Hibernate, JPA, JDBCTemplate и т. д. В базе данных используется база данных MySQL, вы также можете использовать любую базу данных, поддерживающую транзакции, и это не повлияет на результаты проверки.
Сначала мы создаем две таблицы в базе данных:
user1
CREATE TABLE `user1` (
`id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(45) NOT NULL DEFAULT '',
PRIMARY KEY(`id`)
)
ENGINE = InnoDB;
user2
CREATE TABLE `test`.`user2` (
`id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(45) NOT NULL DEFAULT '',
PRIMARY KEY(`id`)
)
ENGINE = InnoDB;
Затем напишите соответствующий код уровня Bean и DAO:
User1
public class User1 {
private Integer id;
private String name;
//get和set方法省略...
}
User2
public class User2 {
private Integer id;
private String name;
//get和set方法省略...
}
User1Mapper
public interface User1Mapper {
int insert(User1 record);
User1 selectByPrimaryKey(Integer id);
//其他方法省略...
}
User2Mapper
public interface User2Mapper {
int insert(User2 record);
User2 selectByPrimaryKey(Integer id);
//其他方法省略...
}
Наконец, конкретный код проверки реализуется сервисным уровнем, и мы перечислим их по ситуации ниже.
1.PROPAGATION_REQUIRED
Добавляем соответствующие методы User1Service и User2ServicePropagation.REQUIRED
Атрибуты.
Метод User1Service:
@Service
public class User1ServiceImpl implements User1Service {
//省略其他...
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void addRequired(User1 user){
user1Mapper.insert(user);
}
}
Метод User2Service:
@Service
public class User2ServiceImpl implements User2Service {
//省略其他...
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void addRequired(User2 user){
user2Mapper.insert(user);
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void addRequiredException(User2 user){
user2Mapper.insert(user);
throw new RuntimeException();
}
}
1.1 Сценарий 1
Периферийный метод этого сценария не запускает транзакцию.
Способ проверки 1:
@Override
public void notransaction_exception_required_required(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addRequired(user2);
throw new RuntimeException();
}
Метод проверки 2:
@Override
public void notransaction_required_required_exception(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addRequiredException(user2);
}
Выполняем метод проверки отдельно, результат:
серийный номер метода проверки | Результаты базы данных | Анализ результатов |
---|---|---|
1 | Вставлены «Чжан Сан» и «Ли Си». | Периферийный метод не открывает транзакцию, а вставленные методы «Чжан Сан» и «Ли Си» работают независимо друг от друга. Исключение периферийного метода не влияет на независимую транзакцию «Чжан Сан» и «Ли Си». Методы Si", вставленные внутрь. |
2 | «Чжан Сан» вставлено, «Ли Си» не вставлено. | Периферийный метод не имеет транзакции, а методы вставки «Чжан Сан» и «Ли Си» выполняются независимо в своих собственных транзакциях, поэтому, если метод «Ли Си» выдает исключение, он откатывает только «Ли Си». метод, вставьте "Чжан Сан" "метод не влияет. |
Вывод: с помощью этих двух методов мы доказываем, что в случае, когда периферийный метод не открывает транзакциюPropagation.REQUIRED
Декорированный внутренний метод заново откроет собственную транзакцию, а открытые транзакции не зависят друг от друга и не мешают друг другу.
1.2 Сценарий 2
Периферийный метод запускает транзакцию, что является сценарием с относительно высокой частотой использования.
Способ проверки 1:
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transaction_exception_required_required(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addRequired(user2);
throw new RuntimeException();
}
Метод проверки 2:
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transaction_required_required_exception(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addRequiredException(user2);
}
Метод проверки 3:
@Transactional
@Override
public void transaction_required_required_exception_try(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
try {
user2Service.addRequiredException(user2);
} catch (Exception e) {
System.out.println("方法回滚");
}
}
Выполняем метод проверки отдельно, результат:
серийный номер метода проверки | Результаты базы данных | Анализ результатов |
---|---|---|
1 | «Чжан Сан» и «Ли Си» не вставляются. | Внешний метод запускает транзакцию, внутренний метод присоединяется к транзакции внешнего метода, внешний метод выполняет откат, и внутренний метод также выполняет откат. |
2 | «Чжан Сан» и «Ли Си» не вставляются. | Внешний метод запускает транзакцию, внутренний метод присоединяется к транзакции внешнего метода, внутренний метод создает исключение и выполняет откат, а внешний метод обнаруживает исключение и вызывает откат всей транзакции. |
3 | «Чжан Сан» и «Ли Си» не вставляются. | Внешний метод запускает транзакцию, внутренний метод присоединяется к транзакции внешнего метода, а внутренний метод создает исключение и выполняет откат.Даже если метод перехвачен и не обнаружен внешним методом, вся транзакция все равно откатывается. |
Вывод: Приведенные выше экспериментальные результаты показывают, что в случае периферийного метода открытия транзакцииPropagation.REQUIRED
Декорированный внутренний метод будет добавлен к транзакции внешнего метода, всеPropagation.REQUIRED
И декорированный внутренний метод, и внешний метод принадлежат одной и той же транзакции.Пока происходит откат одного метода, откатывается вся транзакция.
2.PROPAGATION_REQUIRES_NEW
Добавляем соответствующие методы User1Service и User2ServicePropagation.REQUIRES_NEW
Атрибуты.
Метод User1Service:
@Service
public class User1ServiceImpl implements User1Service {
//省略其他...
@Override
@Transactional(propagation = Propagation.NESTED)
public void addNested(User1 user){
user1Mapper.insert(user);
}
}
Метод User2Service:
@Service
public class User2ServiceImpl implements User2Service {
//省略其他...
@Override
@Transactional(propagation = Propagation.NESTED)
public void addNested(User2 user){
user2Mapper.insert(user);
}
@Override
@Transactional(propagation = Propagation.NESTED)
public void addNestedException(User2 user){
user2Mapper.insert(user);
throw new RuntimeException();
}
}
2.1 Сценарий 1
Периферийный метод не запускает транзакцию.
Способ проверки 1:
@Override
public void notransaction_exception_nested_nested(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addNested(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addNested(user2);
throw new RuntimeException();
}
Метод проверки 2:
@Override
public void notransaction_nested_nested_exception(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addNested(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addNestedException(user2);
}
Выполняем метод проверки отдельно, результат:
серийный номер метода проверки | Результаты базы данных | Анализ результатов |
---|---|---|
1 | Вставлено «Чжан Сан» и вставлено «Ли Си». | В периферийном методе нет транзакции. Вставка методов "Чжан Сан" и "Ли Си" выполняется независимо в своих собственных транзакциях, а внешний метод выдает исключение и выполняет откат, не затрагивая внутренний метод. |
2 | «Чжан Сан» вставлено, «Ли Си» не вставлено | Периферийный метод не открывает транзакцию. Вставка метода "Чжан Сан" и вставка метода "Ли Си" соответственно открывают свои транзакции. Вставка метода "Ли Си" вызывает исключение и откат, а другие транзакции не затрагиваются . |
Вывод: с помощью этих двух методов мы доказываем, что в случае, когда периферийный метод не открывает транзакциюPropagation.REQUIRES_NEW
Декорированный внутренний метод заново откроет собственную транзакцию, а открытые транзакции не зависят друг от друга и не мешают друг другу.
2.2 Сценарий 2
Периферийный метод запускает транзакцию.
Способ проверки 1:
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transaction_exception_required_requiresNew_requiresNew(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addRequiresNew(user2);
User2 user3=new User2();
user3.setName("王五");
user2Service.addRequiresNew(user3);
throw new RuntimeException();
}
Метод проверки 2:
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transaction_required_requiresNew_requiresNew_exception(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addRequiresNew(user2);
User2 user3=new User2();
user3.setName("王五");
user2Service.addRequiresNewException(user3);
}
Метод проверки 3:
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transaction_required_requiresNew_requiresNew_exception_try(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addRequiresNew(user2);
User2 user3=new User2();
user3.setName("王五");
try {
user2Service.addRequiresNewException(user3);
} catch (Exception e) {
System.out.println("回滚");
}
}
Выполняем метод проверки отдельно, результат:
серийный номер метода проверки | Результаты базы данных | Анализ результатов |
---|---|---|
1 | «Чжан Сан» не вставляется, вставляется «Ли Си» и вставляется «Ван Ву». | Периферийный метод запускает транзакцию, вставляет метод «Чжан Сан» и периферийный метод как транзакцию, вставляет метод «Ли Си» и вставляет метод «Ван Ву» в отдельные новые транзакции, а периферийный метод генерирует исключение и только откатывается так же, как периферийный метод.Метод транзакции, поэтому метод вставки «Чжан Сан» откатывается. |
2 | «Чжан Сан» не вставлено, «Ли Си» вставлено и «Ван Ву» не вставлено. | Периферийный метод открывает транзакцию, вставляет метод «Чжан Сан» и периферийный метод в качестве транзакции, а также вставляет метод «Ли Си» и метод «Ван Ву» в отдельные новые транзакции. Вставка метода «Ван Ву» вызывает исключение, транзакция, которая первой вставляет метод «Ван Ву», откатывается, исключение продолжает создаваться и воспринимается периферийным методом, транзакция периферийного метода также откатывается. назад, поэтому метод вставки «Чжан Сан» также откатывается. |
3 | «Чжан Сан» вставлено, «Ли Си» вставлено, а «Ван Ву» не вставлено. | Периферийный метод открывает транзакцию, вставляет метод «Чжан Сан» и периферийный метод в качестве транзакции, а также вставляет метод «Ли Си» и метод «Ван Ву» в отдельные новые транзакции. Вставка метода «Ван Ву» вызывает исключение. Во-первых, транзакция, вставленная в метод «Ван Ву», откатывается. Исключение перехватывается и не воспринимается периферийным методом, а транзакция периферийного метода не откатился.Поэтому вставка методом "Чжан Сан" проходит успешно. |
Вывод: В случае, когда периферийный метод открывает транзакциюPropagation.REQUIRES_NEW
Модифицированный внутренний метод по-прежнему будет открывать независимую транзакцию независимо, а также не зависит от транзакции внешнего метода.Внутренние методы, внутренние методы и транзакции внешнего метода независимы друг от друга и не мешают друг другу.
3.PROPAGATION_NESTED
3.1 Сценарий 1
Периферийный метод этого сценария не запускает транзакцию.
Способ проверки 1:
@Override
public void notransaction_exception_required_required(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addRequired(user2);
throw new RuntimeException();
}
Метод проверки 2:
@Override
public void notransaction_required_required_exception(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addRequiredException(user2);
}
Выполняем метод проверки отдельно, результат:
серийный номер метода проверки | Результаты базы данных | Анализ результатов |
---|---|---|
1 | Вставлены «Чжан Сан» и «Ли Си». | Периферийный метод не открывает транзакцию, а вставленные методы «Чжан Сан» и «Ли Си» работают независимо друг от друга. Исключение периферийного метода не влияет на независимую транзакцию «Чжан Сан» и «Ли Си». Методы Si", вставленные внутрь. |
2 | «Чжан Сан» вставлено, «Ли Си» не вставлено. | Периферийный метод не имеет транзакции, а методы вставки «Чжан Сан» и «Ли Си» выполняются независимо в своих собственных транзакциях, поэтому, если метод «Ли Си» выдает исключение, он откатывает только «Ли Си». метод, вставьте "Чжан Сан" "метод не влияет. |
Вывод: с помощью этих двух методов мы доказываем, что в случае, когда периферийный метод не открывает транзакциюPropagation.NESTED
иPropagation.REQUIRED
Эффект тот же, измененные внутренние методы будут заново открывать свои собственные транзакции, а открытые транзакции независимы друг от друга и не мешают друг другу.
3.2 Сценарий 2
Периферийный метод запускает транзакцию.
Способ проверки 1:
@Transactional
@Override
public void transaction_exception_nested_nested(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addNested(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addNested(user2);
throw new RuntimeException();
}
Метод проверки 2:
@Transactional
@Override
public void transaction_nested_nested_exception(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addNested(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addNestedException(user2);
}
Метод проверки 3:
@Transactional
@Override
public void transaction_nested_nested_exception_try(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addNested(user1);
User2 user2=new User2();
user2.setName("李四");
try {
user2Service.addNestedException(user2);
} catch (Exception e) {
System.out.println("方法回滚");
}
}
Выполняем метод проверки отдельно, результат:
серийный номер метода проверки | Результаты базы данных | Анализ результатов |
---|---|---|
1 | «Чжан Сан» и «Ли Си» не вставляются. | Внешний метод запускает транзакцию, внутренняя транзакция является подтранзакцией внешней транзакции, внешний метод выполняет откат, и внутренний метод также выполняет откат. |
2 | «Чжан Сан» и «Ли Си» не вставляются. | Внешний метод запускает транзакцию, внутренняя транзакция является подтранзакцией внешней транзакции, внутренний метод создает исключение и выполняет откат, а внешний метод обнаруживает исключение и вызывает откат всей транзакции. |
3 | «Чжан Сан» вставлено, «Ли Си» не вставлено. | Периферийный метод запускает транзакцию, а внутренняя транзакция является подтранзакцией периферийной транзакции.Вставка внутреннего метода «Чжан Сан» вызывает исключение, и подтранзакция может быть отброшена отдельно. |
Вывод: Приведенные выше экспериментальные результаты показывают, что в случае периферийного метода открытия транзакцииPropagation.NESTED
Декорированный внутренний метод принадлежит подтранзакции внешней транзакции, периферийная основная транзакция откатывается, подтранзакция должна быть отброшена, а внутренняя подтранзакция может быть отброшена отдельно, не затрагивая периферийную основную транзакцию и другие субтранзакции
4. Сходства и различия между REQUIRED, REQUIRES_NEW, NESTED
Сравнивая «1.2 Сценарий 2» и «3.2 Сценарий 2», мы можем узнать, что:
Внутренние методы, измененные NESTED и REQUIRED, относятся к транзакции внешнего метода.Если внешний метод выдает исключение, транзакция этих двух методов будет отброшена. Но ТРЕБУЕТСЯ присоединиться к транзакции периферийного метода, поэтому он принадлежит к той же транзакции, что и периферийная транзакция.Как только транзакция ТРЕБУЕТСЯ выдает исключение и откатывается, транзакция периферийного метода также будет отброшена. NESTED является подтранзакцией периферийного метода и имеет отдельную точку сохранения, поэтому метод NESTED выдает исключение и откатывается, не затрагивая транзакцию периферийного метода.
Из сравнения «2.2 Сценарий 2» и «3.2 Сценарий 2» мы можем узнать, что:
И NESTED, и REQUIRES_NEW могут откатывать транзакцию внутреннего метода, не затрагивая транзакцию внешнего метода. Но поскольку NESTED является вложенной транзакцией, после отката внешнего метода подтранзакции, которые являются транзакциями внешнего метода, также будут откатываться. В то время как REQUIRES_NEW реализуется путем открытия новой транзакции, внутренняя транзакция и периферийная транзакция являются двумя транзакциями, и откат периферийной транзакции не повлияет на внутреннюю транзакцию.
5. Другие способы распространения транзакций
Ввиду объема статьи тесты других вариантов поведения распространения транзакций здесь не описываются.Заинтересованные читатели могут перейти к исходному коду, чтобы найти соответствующий тестовый код и объяснение результата. Портал:GitHub.com/TMT-colored/внезапно. …
в заключении
Благодаря приведенному выше введению я считаю, что у вас есть более глубокое понимание поведения распространения транзакций Spring, и я надеюсь, что вы можете помочь.