Springboot2(47)註解事務聲明式事務

源碼地址

springboot2教程系列

springboot的事務也主要分爲兩大類,一是xml聲明式事務,二是註解事務,註解事務也可以實現類似聲明式事務的方法,關於註解聲明式事務,目前網上搜索不到合適的資料,所以在這裏,我將自己查找和總結的幾個方法寫到這裏,大家共同探討

引入依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.2</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.12</version>
</dependency>

xml事務

可以使用 @ImportResource(“classpath:transaction.xml”) 引入該xml的配置

xml的配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="txManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" ></property>
    </bean>
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="query*" propagation="SUPPORTS" read-only="true" ></tx:method>
            <tx:method name="get*" propagation="SUPPORTS" read-only="true" ></tx:method>
            <tx:method name="select*" propagation="SUPPORTS" read-only="true" ></tx:method>
            <tx:method name="insert*" propagation="REQUIRED" read-only="true" ></tx:method>
            <tx:method name="*" propagation="REQUIRED" rollback-for="Exception" ></tx:method>
        </tx:attributes>
    </tx:advice>
    <aop:config>
        <aop:pointcut id="allManagerMethod" expression="execution (* cn.myframe..service.*.*(..))" />
        <aop:advisor advice-ref="txAdvice" pointcut-ref="allManagerMethod" order="0" />
    </aop:config>

</beans>

啓動類

@SpringBootApplication
@ImportResource("classpath:transaction.xml")
public class TxApplication {
	public static void main(String[] args){
        SpringApplication app = new SpringApplication(TxApplication.class);
        app.run(args);
    }
}

數據源

@Configuration
public class DruidConfig {

    @Bean("dataSource")
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource getDataSource(){
        return new DruidDataSource();
    }

}

註解開啓事務

1、Transactional註解事務

需要在進行事物管理的方法上添加註解@Transactional,或者偷懶的話直接在類上面添加該註解,使得所有的方法都進行事物的管理,但是依然需要在需要事務管理的類上都添加,工作量比較大

@Transactional
public void insert(BusReceiverEntity receiverEntity) {
    receiverDao.insert(receiverEntity);
    throw new NullPointerException();
}

@Transactional 註解的屬性介紹

propagation 屬性(以下面有詳細解說)

事務的傳播行爲,默認值爲 Propagation.REQUIRED。

isolation 屬性

事務的隔離級別,默認值爲 Isolation.DEFAULT。
可選的值有:

  • Isolation.DEFAULT 使用底層數據庫默認的隔離級別。
  • Isolation.READ_UNCOMMITTED
  • Isolation.READ_COMMITTED
  • Isolation.REPEATABLE_READ
  • Isolation.SERIALIZABLE
timeout 屬性

事務的超時時間,默認值爲-1。如果超過該時間限制但事務還沒有完成,則自動回滾事務。

readOnly 屬性

指定事務是否爲只讀事務,默認值爲 false;爲了忽略那些不需要事務的方法,比如讀取數據,可以設置 read-only 爲 true。

rollbackFor 屬性

用於指定能夠觸發事務回滾的異常類型,可以指定多個異常類型。

noRollbackFor 屬性

拋出指定的異常類型,不回滾事務,也可以指定多個異常類型。

2、註解聲明式事務

@Configuration
public class TxAnoConfig {

    private static final int TX_METHOD_TIMEOUT = 5;
    private static final String AOP_POINTCUT_EXPRESSION = "execution (* cn.myframe..service.*.*(..))";

    @Autowired
    private PlatformTransactionManager transactionManager;

    @Bean
    public TransactionInterceptor txAdvice() {
        NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
        /*只讀事務,不做更新操作*/
        RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
        readOnlyTx.setReadOnly(true);
        readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED );
        /*當前存在事務就使用當前事務,當前不存在事務就創建一個新的事務*/
        RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute();
        requiredTx.setRollbackRules(
            Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
        requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        requiredTx.setTimeout(TX_METHOD_TIMEOUT);
        Map<String, TransactionAttribute> txMap = new HashMap<>();
        txMap.put("add*", requiredTx);
        txMap.put("save*", requiredTx);
        txMap.put("insert*", requiredTx);
        txMap.put("update*", requiredTx);
        txMap.put("delete*", requiredTx);
        txMap.put("get*", readOnlyTx);
        txMap.put("query*", readOnlyTx);
        source.setNameMap( txMap );
        TransactionInterceptor txAdvice = new TransactionInterceptor(transactionManager, source);
        return txAdvice;
    }

    @Bean
    public Advisor txAdviceAdvisor() {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
        return new DefaultPointcutAdvisor(pointcut, txAdvice());
    }

}

或者

@Component
public class TxOtherConfig {
    public static final String transactionExecution = "execution (* cn.myframe..service.*.*(..))";

    @Autowired
    private PlatformTransactionManager transactionManager;

    @Bean
    public TransactionInterceptor transactionInterceptor() {
        Properties attributes = new Properties();
        attributes.setProperty("get*", "PROPAGATION_REQUIRED,-Exception");
        attributes.setProperty("add*", "PROPAGATION_REQUIRED,-Exception");
        attributes.setProperty("insert*", "PROPAGATION_REQUIRED,-Exception");
        attributes.setProperty("update*", "PROPAGATION_REQUIRED,-Exception");
        attributes.setProperty("delete*", "PROPAGATION_REQUIRED,-Exception");
        TransactionInterceptor txAdvice = new TransactionInterceptor(transactionManager, attributes);
        return txAdvice;
    }


    @Bean
    public DefaultPointcutAdvisor defaultPointcutAdvisor(){
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(transactionExecution);
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
        advisor.setPointcut(pointcut);
        Properties attributes = new Properties();
        attributes.setProperty("get*", "PROPAGATION_REQUIRED,-Exception");
        attributes.setProperty("add*", "PROPAGATION_REQUIRED,-Exception");
        attributes.setProperty("insert*", "PROPAGATION_REQUIRED,-Exception");
        attributes.setProperty("update*", "PROPAGATION_REQUIRED,-Exception");
        attributes.setProperty("delete*", "PROPAGATION_REQUIRED,-Exception");
        TransactionInterceptor txAdvice = new TransactionInterceptor(transactionManager, attributes);
        advisor.setAdvice(txAdvice);
        return advisor;
    }
}

事務的傳播說明

@Transactional(propagation = Propagation.REQUIRED)

其中,Propagation有7個常量值,常用的有REQUIRED和SUPPORTS,下面是各種值的解釋:

  • PROPAGATION_REQUIRED:如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中。這是最常見的選擇。
  • PROPAGATION_SUPPORTS:支持當前事務,如果當前沒有事務,就以非事務方式執行。
  • PROPAGATION_MANDATORY:使用當前的事務,如果當前沒有事務,就拋出異常。
  • PROPAGATION_REQUIRES_NEW:新建事務,如果當前存在事務,把當前事務掛起。
  • PROPAGATION_NOT_SUPPORTED:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
  • PROPAGATION_NEVER:以非事務方式執行,如果當前存在事務,則拋出異常。
  • PROPAGATION_NESTED:如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行REQUIRED類似操作。

情況一

@Transactional(propagation = Propagation.REQUIRED)
public void outer(){
}

outer方法在不同事務的傳播等級下的回滾(outer方法發生異常)情況如下

傳播等級 REQUIRED SUPPORTS MANDATORY REQUIRES_NEW NOT_SUPPORTED NEVER NESTED
是否回滾 在進入方法前報錯

情況二

class Out{
    @Transactional(propagation = Propagation.NESTED)
    public void outer(){
        In.inner();
        ......
        throw new NullPointerException();
    }
}

class In{
    @Transactional(propagation = Propagation.REQUIRED)
    public void inner(){
        ......
    }
}
  • 在outer方法以非事務方式執行

    inner回滾情況跟情況一類似

    而且inner的回滾不會導致outer的回滾,無論什麼情況outer都不會回滾

  • 在outer方法以事務方式執行,且outer發生異常的情況下

    outer都會回滾

    inner回滾如下表

    inner的傳播等級 REQUIRED SUPPORTS MANDATORY REQUIRES_NEW NOT_SUPPORTED NEVER NESTED
    是否回滾 執行方法前報錯
  • 在outer方法以事務方式執行,且inner發生異常的情況下

    outer都會回滾,因爲inner方法拋出異常會導致outer也拋出異常觸發回滾

    inner因爲NOT_SUPPORTED才非事務執行所以不回滾,NEVER執行方法時會出現異常,其它的都會回滾

總結:

以事務執行的情況下以發生異常必定會回滾,非事務執行不回滾

同一個事務的情況下,任何一個方法發生異常,都會導致同一事務的所有方法回滾

不同事務的方法發生異常,不會互相影響

注意

在默認的代理模式下,只有目標方法由外部調用,才能被 Spring 的事務攔截器攔截。在同一個類中的兩個方法直接調用,是不會被 Spring 的事務攔截器攔截,就像上面的 outer方法直接調用了同一個類中的 inner方法,inner方法不會被 Spring 的事務攔截器攔截。可以使用 AspectJ 取代 Spring AOP 代理來解決這個問題,但是這裏暫不討論。

@Transactional 事務實現機制

在應用系統調用聲明瞭 @Transactional 的目標方法時,Spring Framework 默認使用 AOP 代理,在代碼運行時生成一個代理對象,根據 @Transactional 的屬性配置信息,這個代理對象決定該聲明 @Transactional 的目標方法是否由攔截器 TransactionInterceptor 來使用攔截,在 TransactionInterceptor 攔截時,會在目標方法開始執行之前創建並加入事務,並執行目標方法的邏輯, 最後根據執行情況是否出現異常,利用抽象事務管理器 AbstractPlatformTransactionManager 操作數據源 DataSource 提交或回滾事務。

Spring AOP 代理有 CglibAopProxyJdkDynamicAopProxy 兩種,以 CglibAopProxy 爲例,對於 CglibAopProxy,需要調用其內部類的 DynamicAdvisedInterceptor 的 intercept 方法。對於 JdkDynamicAopProxy,需要調用其 invoke 方法。

Spring-transaction-mechanis

正如上文提到的,事務管理的框架是由抽象事務管理器 AbstractPlatformTransactionManager 來提供的,而具體的底層事務處理實現,由 PlatformTransactionManager 的具體實現類來實現,如事務管理器 DataSourceTransactionManager。不同的事務管理器管理不同的數據資源 DataSource,比如 DataSourceTransactionManager 管理 JDBC 的 Connection

Spring-TransactionManager-hierarchy-subtypes

常見坑點

使用事務註解@Transactional 之前,應該先了解它的相關屬性,避免在實際項目中踩中各種各樣的坑點。

常見坑點1:遇到檢測異常時,事務默認不回滾。

例如下面這段代碼,賬戶餘額依舊增加成功,並沒有因爲後面遇到SQLException(檢測異常)而進行事務回滾!!

   @Transactional
    public void addMoney() throws Exception {
        //先增加餘額
        accountMapper.addMoney();
        //然後遇到故障
        throw new SQLException("發生異常了..");
    }

原因分析:因爲Spring的默認的事務規則是遇到運行異常(RuntimeException及其子類)和程序錯誤(Error)纔會進行事務回滾,顯然SQLException並不屬於這個範圍。如果想針對檢測異常進行事務回滾,可以在@Transactional 註解裏使用
rollbackFor 屬性明確指定異常。例如下面這樣,就可以正常回滾:

@Transactional(rollbackFor = Exception.class)
public void addMoney() throws Exception {
    //先增加餘額
    accountMapper.addMoney();
    //然後遇到故障
    throw new SQLException("發生異常了..");
}

常見坑點2: 在業務層捕捉異常後,發現事務不生效。

這是許多新手都會犯的一個錯誤,在業務層手工捕捉並處理了異常,你都把異常“吃”掉了,Spring自然不知道這裏有錯,更不會主動去回滾數據。例如:下面這段代碼直接導致增加餘額的事務回滾沒有生效。

@Transactional
public void addMoney() throws Exception {
    //先增加餘額
    accountMapper.addMoney();
    //謹慎:儘量不要在業務層捕捉異常並處理
    try {
        throw new SQLException("發生異常了..");
    } catch (Exception e) {
        e.printStackTrace();
    }
}

不要小瞧了這些細節,往前暴露異常很大程度上很能夠幫我們快速定位問題,而不是經常在項目上線後出現問題,卻無法刨根知道哪裏報錯。

推薦做法:在業務層統一拋出異常,然後在控制層統一處理。

@Transactional
public void addMoney() throws Exception {
    //先增加餘額
    accountMapper.addMoney();
    //推薦:在業務層將異常拋出
    throw new RuntimeException("發生異常了..");
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章