對於Spring相信很多做web開發的小活動一定不陌生,Spring中我們經常談到的就是IOC和AOP,但是對於Spring的事務管理,相信大家一定也很感興趣,今天我們就探討一下Spring中的事務管理。 首先談一下事務使用的場景,我們能想到的最常見場景就是銀行轉賬,A給B轉賬,第一步扣除A中的賬戶 ...
對於Spring相信很多做web開發的小活動一定不陌生,Spring中我們經常談到的就是IOC和AOP,但是對於Spring的事務管理,相信大家一定也很感興趣,今天我們就探討一下Spring中的事務管理。
首先談一下事務使用的場景,我們能想到的最常見場景就是銀行轉賬,A給B轉賬,第一步扣除A中的賬戶金額,第二步將扣除的金額加入到B賬戶,這個過程就要求,這兩步,必須同時成功,如果失敗就需要進行事務回滾,不然我們的數據就會出現異常。這個怎麼來進行實現呢?這就要從今天的事務說起了。首先談一下事務的4大特性:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)。
⑴ 原子性(Atomicity)
原子性是指事務包含的所有操作要麼全部成功,要麼全部失敗回滾,這和前面兩篇博客介紹事務的功能是一樣的概念,因此事務的操作如果成功就必須要完全應用到資料庫,如果操作失敗則不能對資料庫有任何影響。
⑵ 一致性(Consistency)
一致性是指事務必須使資料庫從一個一致性狀態變換到另一個一致性狀態,也就是說一個事務執行之前和執行之後都必須處於一致性狀態。
拿轉賬來說,假設用戶A和用戶B兩者的錢加起來一共是5000,那麼不管A和B之間如何轉賬,轉幾次賬,事務結束後兩個用戶的錢相加起來應該還得是5000,這就是事務的一致性。
⑶ 隔離性(Isolation)
隔離性是當多個用戶併發訪問資料庫時,比如操作同一張表時,資料庫為每一個用戶開啟的事務,不能被其他事務的操作所干擾,多個併發事務之間要相互隔離。
即要達到這麼一種效果:對於任意兩個併發的事務T1和T2,在事務T1看來,T2要麼在T1開始之前就已經結束,要麼在T1結束之後才開始,這樣每個事務都感覺不到有其他事務在併發地執行。
關於事務的隔離性資料庫提供了多種隔離級別,稍後會介紹到。
⑷ 持久性(Durability)
持久性是指一個事務一旦被提交了,那麼對資料庫中的數據的改變就是永久性的,即便是在資料庫系統遇到故障的情況下也不會丟失提交事務的操作。
例如我們在使用JDBC操作資料庫時,在提交事務方法後,提示用戶事務操作完成,當我們程式執行完成直到看到提示後,就可以認定事務以及正確提交,即使這時候資料庫出現了問題,也必須要將我們的事務完全執行完成,否則就會造成我們看到提示事務處理完畢,但是資料庫因為故障而沒有執行事務的重大錯誤。
以上介紹完事務的四大特性(簡稱ACID),現在重點來說明下事務的隔離性,當多個線程都開啟事務操作資料庫中的數據時,資料庫系統要能進行隔離操作,以保證各個線程獲取數據的準確性,在介紹資料庫提供的各種隔離級別之前,我們先看看如果不考慮事務的隔離性,會發生的幾種問題:1、臟讀:臟讀是指在一個事務處理過程里讀取了另一個未提交的事務中的數據。2、不可重覆讀:不可重覆讀是指在對於資料庫中的某個數據,一個事務範圍內多次查詢卻返回了不同的數據值,這是由於在查詢間隔,被另一個事務修改並提交了。3、虛讀(幻讀):幻讀是事務非獨立執行時發生的一種現象。幻讀和不可重覆讀都是讀取了另一條已經提交的事務(這點就臟讀不同),所不同的是不可重覆讀查詢的都是同一個數據項,而幻讀針對的是一批數據整體(比如數據的個數)。
說了這麼多,下麵我們我們開始通過程式來簡單實現一下轉賬場景中如何使用事務。
首先我們這裡需要搭建一個Spring下的轉賬場景,我使用的是jdk8+maven+spring+jdbc來模擬轉賬場景:
1、打開eclipse(最新版本的直接支持maven環境),創建我們的maven項目
2、打開pom.xml添加我們的jar依賴:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.edu.hpugs.spring</groupId> <artifactId>springTransation</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>springTransation</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>6.0.6</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.3.11.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> <version>2.5.6</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.11.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-core --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.3.11.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-beans --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.3.11.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-test --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.3.11.RELEASE</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> </project>
這裡我們使用的是mysql資料庫,所以添加mysql驅動jar包;這裡我們使用spring-jdbc連接數據,所以引入spring-jdbc包;其次就是我們的spring相關jar包了
3、編寫我們的service、dao層:
a、service介面:
public interface IBlankService { void transferAccounts(final String outName, final String inName, final double monery); }
b、service實現:
public class BlankServiceImpl implements IBlankService { private IBlankDao blankDao; public void setBlankDao(IBlankDao blankDao) { this.blankDao = blankDao; } public void transferAccounts(String outName, String inName, double monery) { // TODO Auto-generated method stub blankDao.outMonery(outName, monery); int i = 1 / 0; blankDao.inMonery(inName, monery); } }
c、dao介面:
public interface IBlankDao { void outMonery(final String userName, final double monery); void inMonery(final String userName, final double monery); }
d、dao實現:
public class BlankDaoImpl implements IBlankDao { private DataSource dataSource; public DataSource getDataSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public void outMonery(String userName, double monery) { // TODO Auto-generated method stub String sql = "UPDATE userAccount SET monery = monery - ? WHERE name = ?"; JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.update(sql, monery, userName); } public void inMonery(String userName, double monery) { // TODO Auto-generated method stub String sql = "UPDATE userAccount SET monery = monery + ? WHERE name = ?"; JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.update(sql, monery, userName); } }
4、這裡我們通過junit單元測試的方法來調用我們的service:
public class UserTest { private IBlankService blankService; @Before public void before(){ ApplicationContext appletContext = new ClassPathXmlApplicationContext("applicationContext.xml"); blankService = (IBlankService) appletContext.getBean("blankService"); } @Test public void userTest(){ blankService.transferAccounts("aa", "bb", 100); } }
5、我們的resource文件:
a、applicationContext.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:context="http://www.springframework.org/schema/context" 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/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd"> <context:property-placeholder location="classpath:db.properties"/> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.dirverClass}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.user}" /> <property name="password" value="${jdbc.password}" /> </bean> <bean id="blankService" class="com.edu.hpugs.spring.BlankServiceImpl"> <property name="blankDao" ref="blankDao"></property> </bean> <bean id="blankDao" class="com.edu.hpugs.spring.BlankDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> </beans>
b、db.properties
jdbc.dirverClass=com.mysql.jdbc.Driver jdbc.url=jdbc\:mysql\://127.0.0.1\:3306/blank?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC jdbc.user=root jdbc.password=root
c、log4j.properties
#log4j.rootCategory=error,stdout
#
#log4j.appender.stdout=org.apache.log4j.ConsoleAppender
#log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
#log4j.appender.stdout.layout.ConversionPattern=[HYYT] %p [%t] %C.%M(%L) | %m%n
#
#log4j.logger.com.opensymphony.xwork2.ognl.OgnlValueStack=ERROR
##########################\u914d\u7f6erootLogger--\u8f93\u51fa\u6d88\u606f\u7ea7\u522b\uff1aINFO, WARN, ERROR\u548c FATAL ###############
#log4j.rootLogger = ALL, STUDIO, INFO_FILE, WARN_FILE, ERROR_FILE, FATAL_FILE
#####################################\u63a7\u5236\u53f0\u8f93\u51fa##################################################
log4j.rootLogger = ALL, INFO_FILE, WARN_FILE, ERROR_FILE
###############################################################################################
log4j.appender.STUDIO = org.apache.log4j.ConsoleAppender
log4j.appender.STUDIO.Targer = System.out
log4j.appender.STUDIO.Threshold = INFO
log4j.appender.STUDIO.ImmediateFlush = TRUE
log4j.appender.STUDIO.layout = org.apache.log4j.PatternLayout
log4j.appender.STUDIO.layout.ConversionPattern = [%-5p][%-22d{yyyy/MM/dd HH\:mm\:ss}][%l]%n%m%n
####################################INFO_FILE\u8f93\u51fa################################################
log4j.appender.INFO_FILE = org.apache.log4j.DailyRollingFileAppender
#log4j.appender.INFO_FILE.Targer = /WebLogs/Info_File
log4j.appender.INFO_FILE.File = /WebLogs/springTransation/Info_File/log.txt
log4j.appender.INFO_FILE.Threshold = INFO
log4j.appender.INFO_FILE.ImmediateFlush = TRUE
log4j.appender.INFO_FILE.Append = TRUE
log4j.appender.INFO_FILE.Encoding = UTF-8
log4j.appender.WARN_FILE.DataPattern = '.'YYYY-MM-dd
log4j.appender.INFO_FILE.layout = org.apache.log4j.PatternLayout
log4j.appender.INFO_FILE.layout.ConversionPattern = [%-5p][%-22d{yyyy/MM/dd HH:mm:ss}][%l]%n%m%n
##################################WARN_FILE\u8f93\u51fa#################################################
log4j.appender.WARN_FILE = org.apache.log4j.DailyRollingFileAppender
log4j.appender.WARN_FILE.Targer = /WebLogs/Warn_File
log4j.appender.WARN_FILE.File = /WebLogs/springTransation/Warn_File/log.txt
log4j.appender.WARN_FILE.Threshold = WARN
log4j.appender.WARN_FILE.ImmediateFlush = TRUE
log4j.appender.WARN_FILE.Append = TRUE
log4j.appender.WARN_FILE.Encoding = UTF-8
log4j.appender.WARN_FILE.DataPattern = '.'YYYY-MM-dd
log4j.appender.WARN_FILE.layout = org.apache.log4j.PatternLayout
log4j.appender.WARN_FILE.layout.ConversionPattern = [%-5p][%-22d{yyyy/MM/dd HH:mm:ss}][%l]%n%m%n
##################################ERROR_FILE\u8f93\u51fa#################################################
log4j.appender.ERROR_FILE = org.apache.log4j.DailyRollingFileAppender
log4j.appender.ERROR_FILE.Targer = /WebLogs/Error_File
log4j.appender.ERROR_FILE.File = /WebLogs/springTransation/Error_File/log.txt
log4j.appender.ERROR_FILE.Threshold = ERROR
log4j.appender.ERROR_FILE.ImmediateFlush = TRUE
log4j.appender.ERROR_FILE.Append = TRUE
log4j.appender.ERROR_FILE.Encoding = UTF-8
log4j.appender.ERROR_FILE.DataPattern = '.'YYYY-MM-dd
log4j.appender.ERROR_FILE.layout = org.apache.log4j.PatternLayout
log4j.appender.ERROR_FILE.layout.ConversionPattern = [%-5p][%-22d{yyyy/MM/dd HH:mm:ss}][%l]%n%m%n
##################################FATAL_FILE\u8f93\u51fa################################################
log4j.appender.FATAL_FILE = org.apache.log4j.DailyRollingFileAppender
log4j.appender.FATAL_FILE.Targer = /WebLogs/Fatal_File
log4j.appender.FATAL_FILE.File = /WebLogs/springTransation/Fatal_File/log.txt
log4j.appender.FATAL_FILE.Threshold = FATAL
log4j.appender.FATAL_FILE.ImmediateFlush = TRUE
log4j.appender.FATAL_FILE.Append = TRUE
log4j.appender.FATAL_FILE.Encoding = UTF-8
log4j.appender.FATAL_FILE.DataPattern = '.'YYYY-MM-dd
log4j.appender.FATAL_FILE.layout = org.apache.log4j.PatternLayout
log4j.appender.FATAL_FILE.layout.ConversionPattern = [%-5p][%-22d{yyyy/MM/dd HH:mm:ss}][%l]%n%m%n
到這裡我們的轉賬場景搭建就算完成了,下麵我們就簡單看一下如何進行事務管理:
Spring中的事務管理分為:1、編程是事務;2、聲明式事務、3、基於註解的事務。
Spring的事務有分為:
傳播行為:
PROPAGATION_REQUIRED--支持當前事務,如果當前沒有事務,就新建一個事務。這是最常見的選擇。
PROPAGATION_SUPPORTS--支持當前事務,如果當前沒有事務,就以非事務方式執行。
PROPAGATION_MANDATORY--支持當前事務,如果當前沒有事務,就拋出異常。
PROPAGATION_REQUIRES_NEW--新建事務,如果當前存在事務,把當前事務掛起。
PROPAGATION_NOT_SUPPORTED--以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
PROPAGATION_NEVER--以非事務方式執行,如果當前存在事務,則拋出異常。
隔離級別:
Serializable:最嚴格的級別,事務串列執行,資源消耗最大;
REPEATABLE READ:保證了一個事務不會修改已經由另一個事務讀取但未提交(回滾)的數據。避免了“臟讀取”和“不可重覆讀取”的情況,但是帶來了更多的性能損失。
READ COMMITTED:大多數主流資料庫的預設事務等級,保證了一個事務不會讀到另一個並行事務已修改但未提交的數據,避免了“臟讀取”。該級別適用於大多數系統。
Read Uncommitted:保證了讀取過程中不會讀取到非法數據。隔離級別在於處理多事務的併發問題。
下麵我們先一起探討一下關於編程式的事務管理,首先我們要明白事務應該載入那一層,我們知道Dao是我們的持久層,service是我們的業務層,而我們的事務應當加在業務層,而非持久層。
1、通過TransactionManager來進行我們的事務管理,修改applicationContext.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:context="http://www.springframework.org/schema/context" 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/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd"> <context:property-placeholder location="classpath:db.properties"/> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.dirverClass}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.user}" /> <property name="password" value="${jdbc.password}" /> </bean> <bean id="blankDao" class="com.edu.hpugs.spring.BlankDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="blankService" class="com.edu.hpugs.spring.BlankServiceImpl"> <property name="blankDao" ref="blankDao"></property> <property name="transactionTemplate" ref="transactionTemplate"></property> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="transactionManager"></property> </bean> </beans>
2、同樣修改serviceImpl.java
public class BlankServiceImpl implements IBlankService { private IBlankDao blankDao; public void setBlankDao(IBlankDao blankDao) { this.blankDao = blankDao; } private TransactionTemplate transactionTemplate; public void setTransactionTemplate(TransactionTemplate transactionTemplate) { this.transactionTemplate = transactionTemplate; } public void transferAccounts(final String outName, final String inName, final double monery) { // TODO Auto-generated method stub transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus arg0) { // TODO Auto-generated method stub blankDao.outMonery(outName, monery); int i = 1 / 0; blankDao.inMonery(inName, monery); } }); } }
到這裡Spring編程式的註解就完成了。源碼下載
下麵我們看一下基於聲明式的事務管理:
1、對於聲明式的事務管理,Spring是基於AOP來實現的,所以我們需要在maven中添加aop相關的依賴
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.3.11.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.10</version> </dependency> <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.10</version> </dependency>
2、將我們的場景代碼恢復如初,然後修改我們的applicationContext.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:context="http://www.springframework.org/schema/context" 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/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd"> <context:property-placeholder location="classpath:db.properties"/> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.dirverClass}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.user}" /> <property name="password" value="${jdbc.password}" /> </bean> <bean id="blankDao" class="com.edu.hpugs.spring.BlankDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="blankService" class="com.edu.hpugs.spring.BlankServiceImpl"> <property name="blankDao" ref="blankDao"></property> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!--配置事務增強(如何管理事務,只讀、讀寫...)--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="out*" propagation="REQUIRED" /> <tx:method name="in*" propagation="REQUIRED" /> <tx:method name="*" /> </tx:attributes> </tx:advice> <!--aop配置,攔截哪些方法(切入點表達式,攔截上面的事務增強)--> <aop:config> <aop:pointcut id="pt" expression="execution(* com.edu.hpugs.spring.BlankServiceImpl.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/> </aop:config> </beans>
好了,到這裡關於Spring中的聲明式事務就介紹完畢。源碼下載
最後時我們基於註解的事務管理
1、基於註解的事務管理,需要我們通過maven添加註解依賴:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>4.3.11.RELEASE</version> </dependency>
2、修改applicationContext.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:context="http://www.springframework.org/schema/context" xmlns:tx