事務管理簡介
Spring 事務管理有兩種方式:一種是編程式事務管理,即通過編寫代碼實現事物管理,包括定義事務的開始,程序正常執行后的事物提交,異常時進行的事務回滾。另一種是基於AOP技術實現的聲明式事務管理,其主要思想是將事務管理作為一個“切面”代碼單獨編寫,我們只用關心核心業務邏輯代碼,然后通過AOP技術將事務管理的“切面”代碼織入到業務類中,聲明式事務管理有包括基於AOP方式的事務管理和基於 @Transactional注解方式的事務管理,聲明式事務管理極大的簡化了編程式事務管理的操作流程,不再需要重復地執行定義事物的開始,程序正常執行后事務提交,異常時進行事物回滾這些繁瑣的操作,而基於 @Transactional注解的聲明式事務又進一步簡化了基於AOP的事務管理,減少了Spring配置代碼。
聲明式事務的缺點在於**只能作用到方法級別**,無法做到像編程式事務那樣能控制到代碼塊級別。
- 事務傳播行為
事務規則也就是事務傳播行為,用於解決業務層方法之間的相互調用的問題題。常見的事物傳播行為可分為以下幾種:
名稱 | 說明 |
---|---|
REQUIRED | 表示當前方法必須運行在一個事物環境中,如果一個現有的事物正在運行,該方法將運行在這個事務中,否則,就要開始一個新的事務 |
REQUIRESNEW | 表示當前方法必須運行在自己的事務里 |
SUPPORTS | 表示當前方法不需要事務處理環境,但如果有一個事務正在運行的話,則這個方法也可以運行在這個事務中 |
MANDATORY | 表示當前方法必須運行在一個事務上下文中,否則就拋出異常 |
NEVER | 表示當前方法不應該運行在一個事務上下文中,否則就拋出異常 |
事務管理的主要任務是事務的創建,事務的回滾和事務的提交,是否需要創建事務及如何創建事務,是由事務傳播行為控制的,通常數據的讀取可以不需要事務管理,或者可以指定為只讀事務,而對於數據的增加,刪除和修改操作,則有必要進行事務管理。如果沒有指定事務的傳播行為,Spring默認采用REQUIRED。
轉賬案例
轉賬環境搭建
- 創建表sql(三個賬戶,初始化金額都為1000)
CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
`money` double DEFAULT NULL,<br>
PRIMARY KEY (`id`)<br>
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;<br>
INSERT INTO `account` VALUES ('1', 'aaa', '1000');<br>
INSERT INTO `account` VALUES ('2', 'bbb', '1000');<br>
INSERT INTO `account` VALUES ('3', 'ccc', '1000');<br>
- 項目所需jar包
因為我的jdk是1.8,如果spring的版本用spring3.X的話,后面會出現參數不匹配異常java.lang.IllegalArgumentException,所以我換成了Spring4。
- 數據庫連接參數
- 日志文件log4j.properties
log4j.rootLogger=INFO,logfile,stdout
#write log into file
log4j.appender.logfile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.logfile.Threshold=warn
log4j.appender.logfile.File=${webapp.root}\\logs\\Transaction.log
log4j.appender.logfile.DatePattern=.yyyy-MM-dd
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=[Transaction] %d{yyyy-MM-dd HH\:mm\:ss} %X{remoteAddr} %X{remotePort} %m %n
#display in console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Threshold=info
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[Transaction] %d{yyyy-MM-dd HH\:mm\:ss} %X{remoteAddr} %X{remotePort} %m %n
- 創建業務層接口和實現類
package com.zzh.demo1;
public interface AccountService {
/**
*
* @param out :轉出賬號
* @param in :轉入賬號
* @param money :轉賬金額
*/
public void transfer(String out,String in,Double money);
}
package com.zzh.demo1;
public class AccountServiceImpl implements AccountService {
//注入DAO類
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(String out, String in, Double money) {
accountDao.outMoney(out, money);
accountDao.inMoney(in, money);
}
}
- 創建DAO層接口和實現類
package com.zzh.demo1;
public interface AccountDao {
/**
* @param out :轉出賬號
* @param money :轉出金額
*/
public void outMoney(String out,Double money);
/**
* @param in :轉入賬號
* @param money :轉入金額
*/
public void inMoney(String in,Double money);
}
這里讓Dao層實現類繼承JdbcDaoSupport,這樣一來就可以將JdbcTemplate注入其中,不過我是直接將DataSource連接池注入其中,這時我的DAO實現類就可以創建一個JdbcTemplate,然后操作數據庫的相關內容。
package com.zzh.demo1;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
/**
* DAO層接口實現類
* 這里使用的是JdbcDaoSupport模板
*
*/
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override
public void outMoney(String out, Double money) {
String sql = "update account set money = money - ? where name = ?";
this.getJdbcTemplate().update(sql,money,out);
}
@Override
public void inMoney(String in, Double money) {
String sql = "update account set money = money + ? where name = ?";
this.getJdbcTemplate().update(sql,money,in);
}
}
當然也可以不選擇讓DAO層繼承JdbcDaoSupport,而是把JdbcTemplate作為他的屬性進行操作,通過setter方法利用JdbcTemplate的構造方法將dataSource傳入。
package com.zzh.demo1;
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
public class AccountDaoImpl implements AccountDao {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource){
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public void outMoney(String out, Double money) {
String sql = "update account set money = money - ? where name = ?";
this.jdbcTemplate.update(sql,money,out);
}
@Override
public void inMoney(String in, Double money) {
String sql = "update account set money = money + ? where name = ?";
this.jdbcTemplate.update(sql, money,in);
}
}
- 配置applicationContext.xml
要將c3p0的連接池注入DAO層實現類中,使其可以創建JdbcTemplate。
<?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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 引入外部屬性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置c3p0連接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置業務層 -->
<bean id="accountService" class="com.zzh.demo1.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!-- 配置DAO層,注入連接池就可以得到jdbc模板-->
<bean id="accountDao" class="com.zzh.demo1.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
- 測試轉賬效果
讓測試運行於Spring測試環境中,並引入配置文件
注意Junit使用spring時,若spring沒加載到log4j就會報以下警告:
log4j:WARN No appenders could be found for logger(org.springframework.test.context.junit4.SpringJUnit4ClassRunner).log4j:WARN Please initialize the log4j system properly.
測試業務層類,在用注解 @Resource(name="accountService")和private AccountService accountService;默認按名稱進行裝配,名稱可以通過name屬性進行指定,如果沒有指定name屬性,當注解寫在字段上時,默認取字段名進行安裝名稱查找,如果注解寫在setter方法上默認取屬性名進行裝配。當找不到與名稱匹配的bean時才按照類型進行裝配。但是需要注意的是,如果name屬性一旦指定,就只會按照名稱進行裝配。 @Resource注解在字段上,這樣就不用寫setter方法了,並且這個注解是屬於J2EE的,減少了與spring的耦合。
package com.zzh.demo1;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTest {
@Resource(name="accountService")
private AccountService accountService;
@Test
public void demo1(){
accountService.transfer("aaa", "bbb", 200d);
}
}
我們讓aaa向bbb轉賬200,查看結果轉賬成功。
此時如果我們故意在業務層實現類轉賬過程當中制造異常,如下所示:
查看結果:
可以看到aaa賬戶的錢減少了,而bbb賬戶的錢沒有增加。因為現在還沒有進行事務管理,所以出現了轉出執行了,而轉入沒有執行這樣的情況。而我們所希望的是轉入與轉出能夠同時成功或者同時失敗,這時我們就需要進行相應的事務管理了。
編程式事務管理
-
PlatformTransactionManager接口
這個接口具體的實現類主要有用SpringJdbc或者Mybatis進行管理的類DataSourceTransactionManager,用Hibernate來進行事務管理的類HibernateTransactionManager -
配置SpringJdbc事務管理器
將dataSource連接池注入管理器中,這時因為jdbc進行管理時需要獲得連接對象,然后用connection.setAutoCommit(false)使其不自動提交,執行完后要conmmit,有異常的話要rollback。能獲取這個連接的就是dataSource連接池,而DataSourceTransactionManager事務管理器是真正進行事務管理的類,而連接池又能獲取到連接對象,所以要將其注入。
<!-- 配置SpringJdbc的事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
-
配置事務管理模板
如果直接用事務管理器來管理的話,代碼會很繁瑣,Spring為了簡化事務管理的代碼,提供了一個事務管理模板類org.springframework.transaction.support.TransactionTemplate。而真正進行事務管理的還是transactionManager,所以要將其注入事務管理模板中。
<!-- 配置事務管理模板 -->
<bean id = "transactionTemplat" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
</bean>
-
業務層實現類中注入事務管理模板
在編程式事務管理中,需要我們在使用事務的時候,手動的編寫代碼。也就是哪個類需要進行事務管理,就將模板注入其中。
//注入事務管理模板
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
然后在配置文件中業務層類里注入模板Bean
<bean id="accountService" class="com.zzh.demo1.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
<!-- 注入事務管理模板 -->
<property name="transactionTemplate" ref="transactionTemplate"/>
</bean>
* 利用模板進行轉賬
業務類的 transfer方法中的轉入和轉出此時存在於一個事務中,需要我們用模板的execute方法傳入TransactionCallback接口,這里我選擇new 一個匿名內部類TransactionCallbackWithoutResult。這個匿名內部類里的一個方法叫做doInTransactionWithoutResult,在里面進行事務的相關操作。
這里要有為注意我划紅線的部分,在JDK 1.8以下的版本會報錯,也就是必須在方法的局部變量前加上final關鍵字:
這是因為**匿名內部類訪問外部的局部變量時該變量必須是final,加上final之后會導致局部變量提升為一種隱式的成員變量,而在匿名類中需要傳遞進來final關鍵字,java希望的是保證內部實現和外在表現的一致性**,正因如此,我內部類 操作事務時使用了外部類的out,in和money局部變量,所以必須要加。
而我使用的是JDK 1.8卻沒有final也沒報錯是因為**java8中已經沒有匿名內部類和局部內部類只能訪問final變量的限制了**。
-
測試結果
先將金額都改為1000,然后轉賬,事務回滾了。也就是達到了一個事務里的操作同時成功或者同時失敗。
聲明式事務管理
在編程式的事務管理中,我們需要手動的去修改業務層的代碼,這樣對以后的開發是不太友好的。而聲明式事務管理是基於AOP技術實現,其主要思想是將事務管理作為一個“切面”代碼單獨編寫,我們只用關心核心業務邏輯代碼,然后通過AOP技術將事務管理的“切面”代碼織入到業務類中。
基於TransactionProxyFactoryBean的方式
- 配置事務管理器
<?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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 引入外部屬性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置c3p0連接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置業務層 -->
<bean id="accountService" class="com.zzh.demo2.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!-- 配置DAO層,注入連接池就可以得到jdbc模板-->
<bean id="accountDao" class="com.zzh.demo2.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
- 配置業務層代理
因為要在業務層上進行事務管理,我們可以產生一個代理類來對業務層的transfer方法進行增強。這個代理類叫做事務代理工廠類,它可以為業務層類產生一個代理對象。其中屬性包括目標對象target即為我們需要去代理的類,然后是注入事務管理器進行事務管理,也就是對代理對象進行事務增強。而進行事務管理還需要一些具體的信息比如傳播行為和隔離級別等等,所以還需配置transactionAttribute屬性,然后再向這個屬性中傳入參數props,也就是鍵值對,key代表的是方法,這里只有一個方法transfer,也可以寫一個 * 代表類中所有方法。而prop的值可以是PROPAGATION(事務的傳播行為),ISOLATIONf(事務的隔離級別) -Exception(發生哪些異常回滾事務) +Exception(發生哪些事務不回滾)。在這里我只配了PROPAGATION_REQUIRED,這個傳播行為代表業務層方法中的轉入轉出調用的時候,是處在一個事務之中。
<!-- 配置業務層代理 -->
<bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 配置目標對象 -->
<property name="target" ref="accountService"/>
<!-- 注入事務管理器 -->
<property name="transactionManager" ref="transactionManager"/>
<!-- 注入事務的屬性 -->
<property name="transactionAttributes">
<props>
<prop key="transfer">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
-
業務層
采取了AOP思想,業務層就不再需要配置多余的代碼了。
package com.zzh.demo2;
public class AccountServiceImpl implements AccountService {
//注入DAO類
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer( String out, String in, Double money) {
accountDao.outMoney(out, money);
int i = 1/0;
accountDao.inMoney(in, money);
}
}
-
向測試類中注入代理類
在編程式事務管理中, @ Resource是業務類,而現在業務類是沒有被增強過的類,所以要替換為業務類增強后的代理類即 @Resource(name="accountServiceProxy")。
package com.zzh.demo2;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext2.xml")
public class SpringDemo2 {
/**
* 注入代理類
*/
@Resource(name="accountServiceProxy")
private AccountService accountService;
@Test
public void demo(){
accountService.transfer("aaa", "bbb", 200d);
}
}
- 測試
當沒有異常時進行轉賬:
當設置異常之后,錢數沒有發生改變,事務回滾了:
- 添加readOnly之后
當設置為只讀后,就不允許進行修改了
基於AspectJ的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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 引入外部屬性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置c3p0連接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置業務層 -->
<bean id="accountService" class="com.zzh.demo3.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!-- 配置DAO層,注入連接池就可以得到jdbc模板-->
<bean id="accountDao" class="com.zzh.demo3.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
- 配置事務的通知
需要有事務的管理器,事務相關的屬性,屬性里面的method即為哪些方法要來執行你的事務,跟這個方法相關的還有相應的事務傳播行為和隔離級別等等。
<!-- 配置事務的增強 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
- 配置切面
在切點當中的表達式第一個 * 代表方法的返回值,AccountService后的+代表他的子類,最后的 * (..)代表任意的方法和參數。切面則由切點和增強構成,即在這個切點上運用這個增強。代表切面的標簽有兩個,一個是 < aop:aspect \> 代表多個切入點和多個通知,另一個是 < aop:advisor \> 代表一個切入點和一個通知,這個例題里只有一個增強“txAdvice”,所以就使用了asvisor標簽。
<!-- 配置切面 -->
<aop:config>
<!-- 配置切點 -->
<aop:pointcut expression="execution(* com.zzh.demo3.AccountService+.*(..))" id="pointcut1"/>
<!-- 配置切面 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
</aop:config>
- aop自動代理
我划線的部分不再需要修改,因為這種基於AspectJ的方式是屬於自動代理,也就是在XML中沒有顯式配置 < aop:aspectj-autoproxy/ \>不代表不使用自動代理,這條配置默認屬性為“false”,表示只代理接口(JDK動態代理),所以如果只想代理接口,可以不用顯式寫出。也就是說現在的 < bean id="accountService" \> 這個類在生成的時候就是一個代理對象了,所以 @Resource(name="accountService")已經是被增強過了。如果還對自動代理不清楚,請看我另一篇文章: [Spring之AOP由淺入深](http://www.cnblogs.com/zhaozihan/p/5953063.html)
- 測試
事務發生了回滾
基於注解配置聲明式事務
- 配置事務管理器
<?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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 引入外部屬性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置c3p0連接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置業務層 -->
<bean id="accountService" class="com.zzh.demo4.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!-- 配置DAO層,注入連接池就可以得到jdbc模板-->
<bean id="accountDao" class="com.zzh.demo4.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
- 開啟注解事務
<tx:annotation-driven transaction-manager="transactionManager"/>
哪個類需要使用事務管理,就在那個類頭加上 @Transactional注解即可。
package com.zzh.demo4;
import org.springframework.transaction.annotation.Transactional;
@Transactional
public class AccountServiceImpl implements AccountService {
//注入DAO類
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer( String out, String in, Double money) {
accountDao.outMoney(out, money);
int i = 1/0;
accountDao.inMoney(in, money);
}
}
- @Transactional 注解中的屬性
直接在注解后面即可添加相應的傳播行為,隔離級別等等
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT)
- 測試結果
事務發生了回滾
Github地址:案例源碼