Стратегия автопроводки и распространения транзакций SpringBoot

Spring

Автопроводка SpringBoot

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Почему после использования SpringBoot мы можем построить веб-сервер всего несколькими строками вышеприведенного кода, что ненамного проще, чем использование SpringMVC до этого.

Секрет этого в том, что@SpringBootApplicationСреди заметок:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM,
				classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

он унаследовал@EnableAutoConfigurationаннотация:

/** 
 * ...
 * Auto-configuration classes are regular Spring {@link Configuration} beans. They are
 * located using the {@link SpringFactoriesLoader} ..
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
	...
}

Глядя на комментарии, мы видим, что класс автоконфигурации также существует в виде обычного Spring Bean. они естьSpringFactoriesLoaderпозиция:

public final class SpringFactoriesLoader {
    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                ...
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }
}

Класс будет загружен из пути к классам"META-INF/spring.factories"Ознакомьтесь со списком классов с автоматическим подключением вspring-boot-autoconfigure.jarОн содержит классы конфигурации со встроенными Tomcat, SpringMVC, транзакциями и другими функциями:

org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration

Политика распространения транзакций

предисловие

метод как измерение

Поскольку транзакции Spring основаны на АОП, транзакции существуют в коде Java с точки зрения методов. Распространение транзакций основано на взаимодействии между несколькими транзакциями, поэтому оно проявляется в коде как один метод транзакции, вызывающий другой метод транзакции (как в следующем кодеsavePersonsвызов методаsaveChildren):

@Service
public class PersonService {

    @Autowired
    private PersonMapper personMapper;
    
    @Transactional
    public void savePersons() {
        Person person = new Person();
        person.setUsername("parent");
        person.setPassword("123");
        personMapper.insertSelective(person);
        
        saveChildren();
    }

    @Transactional
    public void saveChildren() {
        saveChild1();
        saveChild2();
        int i = 1 / 0;
    }

    public void saveChild1() {
        Person person = new Person();
        person.setUsername("child1");
        person.setPassword("456");
        personMapper.insertSelective(person);
    }

    public void saveChild2() {
        Person person = new Person();
        person.setUsername("child2");
        person.setPassword("789");
        personMapper.insertSelective(person);
    }

}

боб это вход

Однако Spring AOP основан на расширении компонента, что означает, что когда вы вызываете метод транзакции компонента (метод, модифицированный аннотацией транзакции), аннотация транзакции может вступить в силу обычным образом. Но если вы вызовете метод транзакции в этом классе, это эквивалентно встраиванию кода метода в текущий метод, то есть аннотация транзакции метода будет проигнорирована.

Например:

@Service
public class PersonService {

    @Autowired
    private PersonMapper personMapper;

    public void savePersons() {
        Person person = new Person();
        person.setUsername("parent");
        person.setPassword("123");
        personMapper.insertSelective(person);
        
        saveChildren();
    }

    @Transactional
    public void saveChildren() {
        saveChild1();
        saveChild2();
        int i = 1 / 0;
    }
}

Приведенный выше код эквивалентен следующему коду (saveChildrenАннотации транзакции метода игнорируются):

@Service
public class PersonService {

    @Autowired
    private PersonMapper personMapper;

    public void savePersons() {
        Person person = new Person();
        person.setUsername("parent");
        person.setPassword("123");
        personMapper.insertSelective(person);
        
        saveChild1();
        saveChild2();
        int i = 1 / 0;
    }
}

Таким образом, один транзакционный/нетранзакционный метод, который мы обсуждаем далее, вызывает другой транзакционный/нетранзакционный метод, оба из которых вызываются на основе ссылки на bean-компонент как на метод, а не черезthisВызов методов в этом классе. Таким образом, мы могли бы также объединить два метода написания библиотек.saveChildrenа такжеsavePersonsПереместитесь в два компонента для тестирования:

@Service
public class PersonService2 {
    @Autowired
    private PersonMapper personMapper;

    public void saveChildren() {
        saveChild1();
        saveChild2();
    }

    public void saveChild1() {
        Person person = new Person();
        person.setUsername("child1");
        person.setPassword("456");
        personMapper.insertSelective(person);
    }

    public void saveChild2() {
        Person person = new Person();
        person.setUsername("child2");
        person.setPassword("789");
        personMapper.insertSelective(person);
    }
}
@Service
public class PersonService {

    @Autowired
    private PersonMapper personMapper;

    @Autowired
    private PersonService2 personService2;

    public void savePersons() {
        Person person = new Person();
        person.setUsername("parent");
        person.setPassword("123");
        personMapper.insertSelective(person);

        personService2.saveChildren();
    }	
}
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class TransTest {

    @Autowired
    private PersonService personService;

    @Test
    public void test() {
        personService.savePersons();
    }
}

Текущая транзакция и транзакция родительского метода

В этой статье вопрос о том, создает ли родительский метод транзакцию, относится не только к вызывающему объекту, который вызывает этот метод, но и к вышестоящему методу в цепочке вызовов методов. текущий метод находится внутри транзакции, то есть транзакция существует, когда выполняется текущий метод.

Перечисление политик

Классы перечисления, связанные со стратегией распространения транзакций Spring, следующие:

package org.springframework.transaction.annotation;

public enum Propagation {
    REQUIRED(0),
    SUPPORTS(1),
    MANDATORY(2),
    REQUIRES_NEW(3),
    NOT_SUPPORTED(4),
    NEVER(5),
    NESTED(6);

    private final int value;

    private Propagation(int value) {
        this.value = value;
    }

    public int value() {
        return this.value;
    }
}

ТРЕБУЕТСЯ - ешьте, если у вас есть еда, покупайте сами, если у вас ее нет

@TransactionalСтратегия распространения аннотаций по умолчанию:REQUIRED:

public @interface Transactional {
    Propagation propagation() default Propagation.REQUIRED;
}
/**
	 * Support a current transaction, create a new one if none exists.
	 * Analogous to EJB transaction attribute of the same name.
	 * <p>This is the default setting of a transaction annotation.
	 */
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),

На это указывает такая аннотация: если текущий метод имеет транзакцию, текущая транзакция поддерживается, если текущий метод не имеет транзакции, создается новая транзакция для собственного использования.

Нет отца, сын самостоятельный

public void savePersons() {
    Person person = new Person();
    person.setUsername("parent");
    person.setPassword("123");
    personMapper.insertSelective(person);

    personService2.saveChildren();
}
@Transactional(propagation = Propagation.REQUIRED)
public void saveChildren() {
    saveChild1();
    saveChild2();
    int i = 1/0;
}

Поскольку родительский метод не аннотирован, при выполнении строки 7 вызывается метод транзакции со стратегией распространения REQUIRED, и он создает новую транзакцию для собственного использования, поэтомуchild1, child2потому что1/0Исключение не будет вставлено, исключение брошено в родительский метод, родительский метод не будет откатывать ранее вставленное, потому что нет делаparent, результат выполнения следующий:

----+----------+----------+
| id | username | password |
+----+----------+----------+
| 23 | parent   | 123      |
+----+----------+----------+

Отец владеет, сын наследует

@Transactional(propagation = Propagation.REQUIRED)
public void savePersons() {
    Person person = new Person();
    person.setUsername("parent");
    person.setPassword("123");
    personMapper.insertSelective(person);

    personService2.saveChildren();
}

@Transactional(propagation = Propagation.REQUIRED)
public void saveChildren() {
    saveChild1();
    saveChild2();
    int i = 1 / 0;
}

@Autowired
private PersonService personService;

@Test
public void test() {
    personService.savePersons();
}

потому чтоtestВызов метода транзакции savePersons бина personService и его стратегия распространения ОБЯЗАТЕЛЬНЫ, поэтому он создает новую транзакцию для собственного использования.При вызове ОБЯЗАТЕЛЬНОГО метода транзакции бина personService2 он обнаруживает, что существует текущая транзакция, и поэтому поддерживает текущую транзакцию. , поэтому вставляются parent, child1, child2. Поскольку они находятся в одной транзакции, все они откатываются после возникновения исключений 1/0:

mysql> select * from person;
Empty set (0.00 sec)

сленг

ОБЯЗАТЕЛЬНО, у начальника (родительский метод) есть еда (иметь дела), я (дочерний метод) ем с начальником (поддерживать текущие дела); у начальника нет еды, я покупаю себе еду

ПОДДЕРЖКА - ешьте, когда есть, голодайте без еды

/**
	 * Support a current transaction, execute non-transactionally if none exists.
	 * Analogous to EJB transaction attribute of the same name.
	 * <p>Note: For transaction managers with transaction synchronization,
	 * {@code SUPPORTS} is slightly different from no transaction at all,
	 * as it defines a transaction scope that synchronization will apply for.
	 * As a consequence, the same resources (JDBC Connection, Hibernate Session, etc)
	 * will be shared for the entire specified scope. Note that this depends on
	 * the actual synchronization configuration of the transaction manager.
	 * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization
	 */
	SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),

Если есть текущая транзакция, текущая транзакция поддерживается, в противном случае текущий метод выполняется нетранзакционным способом.

поддержка отца и сына

Когда родительский метод создаст транзакцию, эффект от использования SUPPORTS и REQUIRED в дочернем методе будет таким же.

@Transactional(propagation = Propagation.REQUIRED)
public void savePersons() {
    Person person = new Person();
    person.setUsername("parent");
    person.setPassword("123");
    personMapper.insertSelective(person);

    personService2.saveChildren();
}

@Transactional(propagation = Propagation.SUPPORTS)
public void saveChildren() {
    saveChild1();
    saveChild2();
    int i = 1 / 0;
}

mysql> select * from person;
Empty set (0.00 sec)

отец без сына

Когда родительский метод не создает транзакцию, то и дочерний метод не будет создавать новую транзакцию по собственной инициативе:

public void savePersons() {
    Person person = new Person();
    person.setUsername("parent");
    person.setPassword("123");
    personMapper.insertSelective(person);

    personService2.saveChildren();
}

@Transactional(propagation = Propagation.SUPPORTS)
public void saveChildren() {
    saveChild1();
    saveChild2();
    int i = 1 / 0;
}

mysql> select * from person;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 29 | parent   | 123      |
| 30 | child1   | 456      |
| 31 | child2   | 789      |
+----+----------+----------+

сленг

ПОДДЕРЖКА: Если у босса есть еда, я ем с ним, если у босса нет еды, я могу только голодать.

ОБЯЗАТЕЛЬНО - должна быть еда

/**
	 * Support a current transaction, throw an exception if none exists.
	 * Analogous to EJB transaction attribute of the same name.
	 */
	MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),

Заставить текущий метод выполняться транзакционным образом: если есть текущая транзакция, то поддерживать текущую транзакцию, в противном случае выдать исключение

поддержка отца и сына

То же самое для НЕОБХОДИМОГО, ПОДДЕРЖКИ, ОБЯЗАТЕЛЬНОГО

забастовка без отца

public void savePersons() {
    Person person = new Person();
    person.setUsername("parent");
    person.setPassword("123");
    personMapper.insertSelective(person);

    personService2.saveChildren();
}

@Transactional(propagation = Propagation.MANDATORY)
public void saveChildren() {
    saveChild1();
    saveChild2();
    int i = 1 / 0;
}

Когда выполняется ОБЯЗАТЕЛЬНЫЙ метод транзакции personService2, обнаруживается, что транзакции нет, поэтому он напрямую генерирует исключение:

org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'

mysql> select * from person;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 32 | parent   | 123      |
+----+----------+----------+

сленг

ОБЯЗАТЕЛЬНО: Когда у босса есть еда, он ест вместе с боссом, когда у босса нет еды, я ухожу. Работайте, только если у вас есть еда

REQUIRES_NEW – Самостоятельность

/**
	 * Create a new transaction, and suspend the current transaction if one exists.
	 * Analogous to the EJB transaction attribute of the same name.
	 * <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box
	 * on all transaction managers. This in particular applies to
	 * {@link org.springframework.transaction.jta.JtaTransactionManager},
	 * which requires the {@code javax.transaction.TransactionManager} to be
	 * made available to it (which is server-specific in standard Java EE).
	 * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
	 */
	REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),

Независимо от того, есть ли текущая транзакция, он создаст новую транзакцию для собственного использования, а если есть текущая транзакция, приостановит текущую транзакцию.

сирота сын самосовершенствование

public void savePersons() {
    Person person = new Person();
    person.setUsername("parent");
    person.setPassword("123");
    personMapper.insertSelective(person);

    personService2.saveChildren();
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveChildren() {
    saveChild1();
    saveChild2();
    int i = 1 / 0;
}

В этом случае REQUIRED_NEW совпадает с REQUIRED.

mysql> select * from person;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 33 | parent   | 123      |
+----+----------+----------+

Отец и сын не редкость

@Transactional(propagation = Propagation.REQUIRED)
public void savePersons() {
    Person person = new Person();
    person.setUsername("parent");
    person.setPassword("123");
    personMapper.insertSelective(person);

    personService2.saveChildren();
    
    int i = 1 / 0;
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveChildren() {
    saveChild1();
    saveChild2();
}

На этот раз я буду1/0Перемещенный в родительский метод, родительский метод имеет транзакцию, поэтому вставка родительского метода будет отменена, но выполнение дочернего метода приостановит текущую транзакцию и создаст новую транзакцию, поэтому вставка дочернего метода все еще действует (после завершения выполнения дочернего метода транзакция родительского метода возобновится. будет автоматически восстановлена)

mysql> select * from person;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 40 | child1   | 456      |
| 41 | child2   | 789      |
+----+----------+----------+

сленг

REQUIRED_NEW: Независимо от того, есть у босса еда или нет, я покупаю еду для себя и не принимаю милостей от других.

NOT_SUPPORTED - не есть, а работать

/**
	 * Execute non-transactionally, suspend the current transaction if one exists.
	 * Analogous to EJB transaction attribute of the same name.
	 * <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box
	 * on all transaction managers. This in particular applies to
	 * {@link org.springframework.transaction.jta.JtaTransactionManager},
	 * which requires the {@code javax.transaction.TransactionManager} to be
	 * made available to it (which is server-specific in standard Java EE).
	 * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
	 */
	NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),

Заставляет текущий код выполняться без транзакций, приостанавливая его, если есть текущая транзакция.

Отец и сын не нужны

@Transactional(propagation = Propagation.REQUIRED)
public void savePersons() {
    Person person = new Person();
    person.setUsername("parent");
    person.setPassword("123");
    personMapper.insertSelective(person);

    personService2.saveChildren();
}

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void saveChildren() {
    saveChild1();
    int i = 1 / 0;
    saveChild2();
}

При выполнении дочернего метода транзакция приостанавливается, поэтому вставка дочернего элемента1 не откатывается, а транзакция возобновляется после возврата к родительскому методу, поэтому вставка родителя откатывается

+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 43 | child1   | 456      |
+----+----------+----------+

Отец не имеет намерения сына

public void savePersons() {
    Person person = new Person();
    person.setUsername("parent");
    person.setPassword("123");
    personMapper.insertSelective(person);

    personService2.saveChildren();
}

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void saveChildren() {
    saveChild1();
    int i = 1 / 0;
    saveChild2();
}

Я не хочу выполнять этот метод транзакционным способом.Если текущей транзакции нет, разве это не будет именно то, что я хочу, чтобы родитель и ребенок1 не откатились

+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 44 | parent   | 123      |
| 45 | child1   | 456      |
+----+----------+----------+

сленг

NOT_SUPPORT: Я не ем независимо от того, есть у босса еда или нет, я машина, которая просто любит работать

НИКОГДА - бить, когда речь идет о еде

/**
	 * Execute non-transactionally, throw an exception if a transaction exists.
	 * Analogous to EJB transaction attribute of the same name.
	 */
	NEVER(TransactionDefinition.PROPAGATION_NEVER),

Выполните этот метод нетранзакционным способом, если есть прямое исключение текущей транзакции.

отец без сына

public void savePersons() {
    Person person = new Person();
    person.setUsername("parent");
    person.setPassword("123");
    personMapper.insertSelective(person);

    personService2.saveChildren();
}

@Transactional(propagation = Propagation.NEVER)
public void saveChildren() {
    saveChild1();
    int i = 1 / 0;
    saveChild2();
}

Этот сценарий имеет тот же эффект, что и неиспользование транзакции.

+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 46 | parent   | 123      |
| 47 | child1   | 456      |
+----+----------+----------+

отец и сын бастуют

@Transactional(propagation = Propagation.REQUIRED)
public void savePersons() {
    Person person = new Person();
    person.setUsername("parent");
    person.setPassword("123");
    personMapper.insertSelective(person);

    personService2.saveChildren();
}

@Transactional(propagation = Propagation.NEVER)
public void saveChildren() {
    saveChild1();
    int i = 1 / 0;
    saveChild2();
}

При вызове подметода, поскольку в данный момент есть транзакция, подметод напрямую выдает исключение, родительская вставка откатывается, и подметод не выполняется, и, естественно, никакие данные не вставляются.

mysql> select * from person;
Empty set (0.00 sec)

сленг

НИКОГДА: когда босс упоминал о еде, я увольнялся.

NESTED

/**
	 * Execute within a nested transaction if a current transaction exists,
	 * behave like {@code REQUIRED} otherwise. There is no analogous feature in EJB.
	 * <p>Note: Actual creation of a nested transaction will only work on specific
	 * transaction managers. Out of the box, this only applies to the JDBC
	 * DataSourceTransactionManager. Some JTA providers might support nested
	 * transactions as well.
	 * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
	 */
	NESTED(TransactionDefinition.PROPAGATION_NESTED);

Если текущей транзакции нет, эффект такой же, как и у REQUIRED, в противном случае этот метод выполняется как вложенные транзакции в текущую транзакцию. Откат внешней транзакции приводит к откату внутренней транзакции (даже если внутренняя транзакция выполняется нормально).

@Transactional(propagation = Propagation.REQUIRED)
public void savePersons() {
    Person person = new Person();
    person.setUsername("parent");
    person.setPassword("123");
    personMapper.insertSelective(person);

    personService2.saveChildren();

    int i = 1 / 0;
}

@Transactional(propagation = Propagation.NESTED)
public void saveChildren() {
    saveChild1();
    saveChild2();
}

Хотя подметод обычно выполняется в виде вложенной транзакции, операция вложенной транзакции должна ожидать фиксации самой внешней транзакции в текущей модели транзакции, которая будет записана в библиотеку, иначе будет выполнен откат с внешняя транзакция. Это следует отличать от REQUIRED_NEW, который приостанавливает текущую транзакцию и создает новую транзакцию (две транзакции не влияют друг на друга), а не создает вложенную транзакцию под текущей транзакцией (вложенная транзакция ограничивается текущей транзакцией). .

Следовательно, фиксация внутренней транзакции будет отменена вместе с внешней транзакцией:

mysql> select * from person;
Empty set (0.00 sec)

сленг

NESTED: если у босса нет еды, он покупает себе еду и ест все, что хочет; у босса есть еда, поэтому он ест вместе с боссом, а что есть, зависит от настроения босса