SpringBoot自動裝配&事務傳播策略

SpringBoot自動裝配

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

爲什麼使用SpringBoot後,咱們只需上述幾行代碼便可搭建一個web服務器,比以前使用SpringMVC不要簡潔太多。java

這其中奧妙在於@SpringBootApplication註解之中:mysql

@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註解:web

/** * ... * 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定位:spring

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、事務等功能的配置類:sql

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):bash

@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的事務方法(被事務註解修飾的方法)時,該事務註解是能夠正常生效的。但若是你調用本類中的事務方法,那就至關於將該方法中的代碼內嵌到當前方法中,即該方法的事務註解會被忽略。服務器

例如:app

@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方法事務註解被忽略掉了):ide

@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中進行測試:spring-boot

@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;
    }
}
複製代碼

REQUIRED——有飯就吃,沒飯本身買

@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調用bean personService的事務方法savePersons且其傳播策略爲REQUIRED,因而其新建一個事務給本身用,當調用bean personService2的REQUIRED事務方法時,發現當前有事務所以支持當前事務,所以parent、child一、child2的插入因爲在同一個事務中,所以在1/0異常拋出後都被回滾:

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

複製代碼

俚語

REQUIRED,老闆(父方法)有飯吃(有事務),我(子方法)跟着老闆吃(支持當前事務);老闆沒飯吃,我本身買飯吃

SUPPORTS——有飯就吃,沒飯餓肚子

/**
	 * 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      |
+----+----------+----------+

複製代碼

俚語

SUPPORTS:老闆有飯吃,我跟着老闆吃;老闆沒飯吃,我就只能餓肚子了。

MANDATORY——必需要有飯吃

/**
	 * 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是同樣的

父無子罷工

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的MANDATORY事務方法時,發現當前沒有事務,因而它直接拋出一個異常:

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

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

複製代碼

俚語

MANDATORY:老闆有飯吃,跟着老闆吃;老闆沒飯吃,老子不幹了。有飯才幹活

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移入了父方法中,父方法有事務所以parent的插入會回滾,可是子方法的執行會掛起當前事務另建新事務,所以子方法的插入依然有效(子方法執行結束後父方法的事務又會被自動恢復)

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();
}

複製代碼

執行子方法時,事務被掛起,所以child1的插入未被回滾,回到父方法後事務被恢復,所以parent的插入被回滾

+----+----------+----------+
| 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();
}

複製代碼

原本就不想以事務的方式執行此方法,若是當前沒有事務,豈不正合我意,因而parent、child1都沒喲回滾

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

複製代碼

俚語

NOT_SUPPORT:無論老闆有沒有飯,我都不吃,我是個只愛幹活的機器

NEVER——一說吃飯就罷工

/** * 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();
}

複製代碼

調用子方法時由於當前有事務,所以子方法直接拋出異常,parent的插入回滾,子方法沒有執行天然沒有插入數據

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

複製代碼

俚語

NEVER:老闆一提吃飯,我就不幹了。

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區分開,REQUIRED_NEW是掛起當前事務另建新事務(兩事務互不影響),而非在當前事務下建嵌套事務(嵌套事務受當前事務的牽制)。

所以,內層事務的提交會和外層事務一同回滾:

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

複製代碼

俚語

NESTED:老闆沒飯吃,本身買飯吃,想吃啥吃啥;老闆有飯吃,跟着老闆吃,吃啥得看老闆心情

相關文章
相關標籤/搜索