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

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 代理有 CglibAopProxy 和 JdkDynamicAopProxy 兩種,以 CglibAopProxy 爲例,對於 CglibAopProxy,需要調用其內部類的 DynamicAdvisedInterceptor 的 intercept 方法。對於 JdkDynamicAopProxy,需要調用其 invoke 方法。

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

常見坑點

使用事務註解@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("發生異常了..");

}

關閉spring boot 關閉spring事務

@Transactional(propagation = Propagation.NOT_SUPPORTED)

@Transactional(propagation=Propagation.REQUIRED)//如果有事務,那麼加入事務,沒有的話新創建一個@Transactional(propagation=Propagation.NOT_SUPPORTED)//這個方法不開啓事務@Transactional(propagation=Propagation.REQUIREDS_NEW)//不管是否存在事務,都創建一個新的事務,原來的掛起,新的執行完畢,繼續執行老的事務<br>@Transactional(propagation=Propagation.MANDATORY)//必須在一個已有的事務中執行,否則拋出異常
@Transactional(propagation=Propagation.NEVER)//不能在一個事務中執行,就是當前必須沒有事務,否則拋出異常@Transactional(propagation=Propagation.SUPPORTS)//其他bean調用這個方法,如果在其他bean中聲明瞭事務,就是用事務。沒有聲明,就不用事務。@Transactional(propagation=Propagation.NESTED)//如果一個活動的事務存在,則運行在一個嵌套的事務中,如果沒有活動的事務,則按照REQUIRED屬性執行,它使用一個單獨的事務。這個書屋擁有多個回滾的保存點,內部事務的回滾不會對外部事務造成影響,它只對DataSource TransactionManager事務管理器起效。
@Transactional(propagation=Propagation.REQUIRED,readOnly=true)//只讀,不能更新,刪除@Transactional(propagation=Propagation.REQUIRED,timeout=30)//超時30秒</p>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章