0%

SpringBoot自动装配&事务传播策略

SpringBoot自动装配

1
2
3
4
5
6
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

为何使用SpringBoot后,我们只需上述几行代码即可搭建一个web服务器,比之前使用SpringMVC不要简洁太多。

这其中奥妙在于@SpringBootApplication注解之中:

1
2
3
4
5
6
7
8
9
10
11
@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注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/** 
* ...
* 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定位:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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、事务等功能的配置类:

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

事务传播策略

前言

方法为维度

由于Spring事务是基于AOP的,所以事务以方法为维度存在于Java代码中。而事务的传播是基于多事务之间相互影响的,所以在代码中表现为一个事务方法调用另一个事务方法(如下列代码中savePersons方法中调用saveChildren):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@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);
}

}

bean为入口

但是,SpringAOP是基于bean增强的,也就是说当你调用一个bean的事务方法(被事务注解修饰的方法)时,该事务注解是可以正常生效的。但如果你调用本类中的事务方法,那就相当于将该方法中的代码内嵌到当前方法中,即该方法的事务注解会被忽略。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@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方法事务注解被忽略掉了):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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调用本类中的方法。因此我们不妨将两个写库方法saveChildrensavePersons移入两个bean中进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@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);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class TransTest {

@Autowired
private PersonService personService;

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

当前事务&父方法事务

本文中说父方法是否创建事务,不仅仅是指调用本方法的调用方,而是泛指方法调用链的上游方法,只要上游方法中的任意一个方法开启了事务,那么当前方法的执行就处于事务之中,也即执行当前方法时存在事务。

策略枚举

Spring事务传播策略相关的枚举类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}
}

REQUIRED——有饭就吃,没饭自己买

@Transactional注解默认的传播策略就是REQUIRED

1
2
3
public @interface Transactional {
Propagation propagation() default Propagation.REQUIRED;
}
1
2
3
4
5
6
/**
* 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),

被该注解表示:如果当前方法有事务,则支持当前事务;如果当前方法没有事务,则新建事务供自己使用。

父无,子自力更生

1
2
3
4
5
6
7
8
public void savePersons() {
Person person = new Person();
person.setUsername("parent");
person.setPassword("123");
personMapper.insertSelective(person);

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

由于父方法没有注解,执行到第7行时,调用了传播策略为REQUIRED的事务方法,其自己新建事务供自己使用,因此child1, child2因为1/0异常不会被插入,异常抛至父方法,父方法因为没有事务所以不会回滚之前插入的parent,执行结果如下:

1
2
3
4
5
----+----------+----------+
| id | username | password |
+----+----------+----------+
| 23 | parent | 123 |
+----+----------+----------+

父有,子继承

1
2
3
4
5
6
7
8
9
@Transactional(propagation = Propagation.REQUIRED)
public void savePersons() {
Person person = new Person();
person.setUsername("parent");
person.setPassword("123");
personMapper.insertSelective(person);

personService2.saveChildren();
}
1
2
3
4
5
6
@Transactional(propagation = Propagation.REQUIRED)
public void saveChildren() {
saveChild1();
saveChild2();
int i = 1 / 0;
}
1
2
3
4
5
6
7
@Autowired
private PersonService personService;

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

由于test调用bean personService的事务方法savePersons且其传播策略为REQUIRED,于是其新建一个事务给自己用,当调用bean personService2的REQUIRED事务方法时,发现当前有事务因此支持当前事务,因此parent、child1、child2的插入由于在同一个事务中,因此在1/0异常抛出后都被回滚:

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

俚语

REQUIRED,老板(父方法)有饭吃(有事务),我(子方法)跟着老板吃(支持当前事务);老板没饭吃,我自己买饭吃

SUPPORTS——有饭就吃,没饭饿肚子

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 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效果是一样的

1
2
3
4
5
6
7
8
9
@Transactional(propagation = Propagation.REQUIRED)
public void savePersons() {
Person person = new Person();
person.setUsername("parent");
person.setPassword("123");
personMapper.insertSelective(person);

personService2.saveChildren();
}
1
2
3
4
5
6
@Transactional(propagation = Propagation.SUPPORTS)
public void saveChildren() {
saveChild1();
saveChild2();
int i = 1 / 0;
}
1
2
mysql> select * from person;
Empty set (0.00 sec)

父无子无

当父方法没有创建事务,那么子方法也不会自作主张去新建事务:

1
2
3
4
5
6
7
8
public void savePersons() {
Person person = new Person();
person.setUsername("parent");
person.setPassword("123");
personMapper.insertSelective(person);

personService2.saveChildren();
}
1
2
3
4
5
6
@Transactional(propagation = Propagation.SUPPORTS)
public void saveChildren() {
saveChild1();
saveChild2();
int i = 1 / 0;
}
1
2
3
4
5
6
7
8
mysql> select * from person;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 29 | parent | 123 |
| 30 | child1 | 456 |
| 31 | child2 | 789 |
+----+----------+----------+

俚语

SUPPORTS:老板有饭吃,我跟着老板吃;老板没饭吃,我就只能饿肚子了。

MANDATORY——必须要有饭吃

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

强制以事务的方式执行当前方法:如果当前有事务,那么支持当前事务,否则抛出异常

父有子支持

这点REQUIRED, SUPPORTS, MANDATORY是一样的

父无子罢工

1
2
3
4
5
6
7
8
public void savePersons() {
Person person = new Person();
person.setUsername("parent");
person.setPassword("123");
personMapper.insertSelective(person);

personService2.saveChildren();
}
1
2
3
4
5
6
@Transactional(propagation = Propagation.MANDATORY)
public void saveChildren() {
saveChild1();
saveChild2();
int i = 1 / 0;
}

当执行到personService2的MANDATORY事务方法时,发现当前没有事务,于是它直接抛出一个异常:

1
org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
1
2
3
4
5
6
mysql> select * from person;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 32 | parent | 123 |
+----+----------+----------+

俚语

MANDATORY:老板有饭吃,跟着老板吃;老板没饭吃,老子不干了。有饭才干活

REQUIRES_NEW——自力更生

1
2
3
4
5
6
7
8
9
10
11
/**
* 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),

不管当前有没有事务,自己都会新建一个事务为自己所用,并且如果当前有事务那么就会挂起当前事务

父无子自强

1
2
3
4
5
6
7
8
public void savePersons() {
Person person = new Person();
person.setUsername("parent");
person.setPassword("123");
personMapper.insertSelective(person);

personService2.saveChildren();
}
1
2
3
4
5
6
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveChildren() {
saveChild1();
saveChild2();
int i = 1 / 0;
}

此情景下,REQUIRED_NEW同REQUIRED

1
2
3
4
5
6
mysql> select * from person;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 33 | parent | 123 |
+----+----------+----------+

父有子也不稀罕

1
2
3
4
5
6
7
8
9
10
11
@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;
}
1
2
3
4
5
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveChildren() {
saveChild1();
saveChild2();
}

这次我将1/0移入了父方法中,父方法有事务因此parent的插入会回滚,但是子方法的执行会挂起当前事务另建新事务,因此子方法的插入依然有效(子方法执行结束后父方法的事务又会被自动恢复)

1
2
3
4
5
6
7
mysql> select * from person;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 40 | child1 | 456 |
| 41 | child2 | 789 |
+----+----------+----------+

俚语

REQUIRED_NEW:不管老板有没有饭吃,我都自己买饭吃,不接受他人的恩惠。

NOT_SUPPORTED——不吃饭只干活

1
2
3
4
5
6
7
8
9
10
11
/**
* 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),

强制以非事务的方式执行当前代码,如果当前有事务则将其挂起。

父有子不用

1
2
3
4
5
6
7
8
9
@Transactional(propagation = Propagation.REQUIRED)
public void savePersons() {
Person person = new Person();
person.setUsername("parent");
person.setPassword("123");
personMapper.insertSelective(person);

personService2.saveChildren();
}
1
2
3
4
5
6
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void saveChildren() {
saveChild1();
int i = 1 / 0;
saveChild2();
}

执行子方法时,事务被挂起,因此child1的插入未被回滚,回到父方法后事务被恢复,因此parent的插入被回滚

1
2
3
4
5
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 43 | child1 | 456 |
+----+----------+----------+

父无遂子意

1
2
3
4
5
6
7
8
public void savePersons() {
Person person = new Person();
person.setUsername("parent");
person.setPassword("123");
personMapper.insertSelective(person);

personService2.saveChildren();
}
1
2
3
4
5
6
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void saveChildren() {
saveChild1();
int i = 1 / 0;
saveChild2();
}

本来就不想以事务的方式执行此方法,如果当前没有事务,岂不正合我意,于是parent、child1都没哟回滚

1
2
3
4
5
6
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 44 | parent | 123 |
| 45 | child1 | 456 |
+----+----------+----------+

俚语

NOT_SUPPORT:不管老板有没有饭,我都不吃,我是个只爱干活的机器

NEVER——一说吃饭就罢工

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

以非事务方式执行此方法,如果当前有事务直接异常

父无子无事

1
2
3
4
5
6
7
8
public void savePersons() {
Person person = new Person();
person.setUsername("parent");
person.setPassword("123");
personMapper.insertSelective(person);

personService2.saveChildren();
}
1
2
3
4
5
6
@Transactional(propagation = Propagation.NEVER)
public void saveChildren() {
saveChild1();
int i = 1 / 0;
saveChild2();
}

此情景和不用事务效果一样

1
2
3
4
5
6
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 46 | parent | 123 |
| 47 | child1 | 456 |
+----+----------+----------+

父有子罢工

1
2
3
4
5
6
7
8
9
@Transactional(propagation = Propagation.REQUIRED)
public void savePersons() {
Person person = new Person();
person.setUsername("parent");
person.setPassword("123");
personMapper.insertSelective(person);

personService2.saveChildren();
}
1
2
3
4
5
6
@Transactional(propagation = Propagation.NEVER)
public void saveChildren() {
saveChild1();
int i = 1 / 0;
saveChild2();
}

调用子方法时因为当前有事务,因此子方法直接抛出异常,parent的插入回滚,子方法没有执行自然没有插入数据

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

俚语

NEVER:老板一提吃饭,我就不干了。

NESTED

1
2
3
4
5
6
7
8
9
10
/**
* 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;否则以当前事务嵌套事务的方式执行此方法。外层事务的回滚会导致内层事务回滚(即使内层事务正常执行)。

1
2
3
4
5
6
7
8
9
10
11
@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;
}
1
2
3
4
5
@Transactional(propagation = Propagation.NESTED)
public void saveChildren() {
saveChild1();
saveChild2();
}

虽然子方法以嵌套事务的方式正常执行,但嵌套事务的操作要等当前事务模型中最外层事务的提交一并写库,否则会跟随外层事务一同回滚。这点要和REQUIRED_NEW区分开,REQUIRED_NEW是挂起当前事务另建新事务(两事务互不影响),而非在当前事务下建嵌套事务(嵌套事务受当前事务的牵制)。

因此,内层事务的提交会和外层事务一同回滚:

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

俚语

NESTED:老板没饭吃,自己买饭吃,想吃啥吃啥;老板有饭吃,跟着老板吃,吃啥得看老板心情

鼓励一下~