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>