Spring程式設計式事務管理和宣告式事務管理 案例
轉賬案例使用了Spring事務管理,用兩種方式實現:程式設計式事務管理和宣告式事物管理。
其中,程式設計式事務管理是一種手動修改程式碼的方式,比較麻煩,在開發過程中很少使用;宣告式事務管理有三種方法實現,分別是TransactionProxyFactoryBean的代理方式、基於AspectJ的xml配置方式和基於註解的宣告方式,後兩種在開發應用中常常出現。
1 一般Spring案例的建立
(原始碼見spring_transaction下的demo)
1.1建立資料庫spring_transaction
CREATE TABLE `account` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL, `money` double DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULTCHARSET=utf8; INSERT INTO `account` VALUES ('1', 'aaa','1000'); INSERT INTO `account` VALUES ('2', 'bbb','1000'); INSERT INTO `account` VALUES ('3', 'ccc','1000');
1.2 MyEclipse上建立專案spring_transaction
1.3、引入Jar包
Spring開發最基本的6個包:
- Com.springsource.org.apache.commons.logging-1.1.1.jar
- Com.springsource.org.apache.log4j-1.2.15.jar
- Apring-beans-3.2.2.Release.jar
- Spring-context-3.2.0.Release.jar
- Spring-core-3.2.0.Release.jar
- Spring-expression-3.2.0.Release.jar
連線資料庫、整合單元測試的包:
- Junit4.jar
- spring-jdbc-3.2.0.RELEASE.jar
- spring-test-3.2.0.RELEASE.jar
- spring-tx-3.2.0.RELEASE.jar
1.4 在src目錄下編寫配置配置檔案
1.4.1 日誌記錄 Log4j.properties
# $Id: Action.java 5022962007-02-01 17:33:39Z niallp $ # # Licensed to the ApacheSoftware Foundation (ASF) under one # or more contributor licenseagreements. See the NOTICE file # distributed with this work foradditional information # regarding copyrightownership. The ASF licenses this file # to you under the ApacheLicense, Version 2.0 (the # "License"); you may notuse this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable lawor agreed to in writing, # software distributed under theLicense is distributed on an # "AS IS" BASIS, WITHOUTWARRANTIES OR CONDITIONS OF ANY # KIND, either express orimplied. See the License for the # specific language governingpermissions and limitations # under the License. log4j.rootLogger = WARN, stdout log4j.appender.stdout = org.apache.log4j.ConsoleAppender log4j.appender.stdout.Threshold = WARN log4j.appender.stdout.Target = System.out log4j.appender.stdout.layout = org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern = %d{ISO8601} %-5p [%F:%L] : %m%n
1.4.2 配置資料庫連線配置檔案jdbc.properties,交代清楚資料庫的驅動類、資料庫連線地址、賬戶名和密碼。
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///spring_transaction
jdbc.username=root
jdbc.password=root
1.4.3 核心配置檔案 applicationContext.xml
Spring一般的核心配置檔案分為幾個部分:
第一部分:是spring開發最全的空間約束條件。
第二部分:引入外部屬性檔案jdbc.properties,方便連線資料庫。
第三部分:配置c3p0連線池,連線jdbc.properties的四個屬性,用於框架配置檔案連線資料庫。
第四部分:配置業務層類,同時注入屬性Dao方便自動匹配,用於完成操作;並注入事務管理模板transactionTemplate,用於後面的事物管理。
第五部分:配置Dao層的類,同時注入連線池,方便連線池連線資料庫得到的資訊注入到對應的Dao。
<?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/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"> <!-- 上文最Spring開發最全的約束 --> <!-- 引入外部屬性檔案--> <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="cn.terence.spring.demo1.AccountServiceImpl"> <property name="accountDao"ref="accountDao"/> <!-- 注入事務管理模板 --> <property name="transactionTemplate"ref="transactionTemplate"/> </bean> <!-- 配置Dao層的類 --> <bean id="accountDao" class="cn.terence.spring.demo1.AccountDaoImpl"> <!-- 注入連線池,可以利用jdbc的jar包裡面的連線池建立模板 --> <property name="dataSource"ref="dataSource"/> </bean> </beans>
1.5 編寫類
1.5.1編寫DAO,注入JdbcTemplate
/** * 轉賬案例Dao層實現類 * @author Terence *繼承JdbcDaoSupport模板給Dao層實現類 */ public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { public void inMoney(String out, Double money) { String sql="update account setmoney=money-? where name=?"; this.getJdbcTemplate().update(sql,money,out); } public void outMoney(String in, Double money) { String sql="update account setmoney=money+? where name=?"; this.getJdbcTemplate().update(sql,money,in); } }
1.5.2 編寫Service,注入DAO
public class AccountServiceImpl implements AccountService { //注入轉賬的Dao private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } /** * 一般的執行操作情況,沒有新增事務管理 * 將操作繫結在一起,如果遇到異常,則發生事務回滾 * final型變數,內部使用 * @param out * @param in * @param money */ public void transfer(String out,String in,Double money) { accountDao.outMoney(out, money); //int i=1/0; accountDao.inMoney(in, money); } }
1.5.3 編寫測試類springDemo
/** * @author Terence */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class SpringDemo { @Resource(name="accountService") private AccountService accountService; /* * 轉賬案例測試 */ @Test public void demo() { accountService.transfer("aaa", "bbb", 300d); }
測試執行類,通過@RunWith 和@Test載入測試類和測試方法,通過@ContextConfiguration載入核心配置檔案,核心配置檔案在空間約束下,載入資料庫連線檔案jdbc.properties,通過連線池匹配資料庫連線檔案裡面的屬性連線資料庫;通過@Resource利用核心配置檔案,自動注入Dao,並獲取service物件accountService;然後通過測試demo()方法,測試Service物件的transfer()方法。
1.6 沒有spring事務管理的執行情況
測試這個spring框架下的一般案例,執行demo()方法的transfer操作:在業務層,轉賬有兩個操作,轉出和轉入,一般情況下先轉出accountDao.outMoney(out, money),後轉入accountDao.inMoney(in, money),這樣資料庫中的資料是沒有什麼錯誤的,但是如果在轉出和轉入操作之間需要加入一些其他的業務操作或執行,那麼這中間的就可能出現錯誤丟擲異常,導致程式停在中間,不能繼續執行轉入操作,資料庫裡的資料出現了不一致的情況 ,比如1除以0的分母為0的異常。
此時,就需要用Spring框架下的事物對其進行管理,通過事物管理器定義其傳播行為、隔離級別等,控制轉賬案例的轉出和轉入兩種操作,可以將事物回滾到轉出操作之前的狀態,保持資料庫裡的資料一致。
下面用兩種事務管理方法來進行spring事務管理。
2 程式設計式事務管理-很少使用
通過程式設計式事物管理,手動修改程式碼,可以將轉賬案例的兩種操作和中間的其他操作放在spring事物管理模板中,放在同一個事務中,一旦出現異常,則回滾到事物執行的最初狀態,以此保證資料的完整性、一致性。
(原始碼見spring_transaction下的demo1)
2.1 配置事務管理器,定義事務管理模板
在1.4.3 核心配置檔案applicationContext.xml裡配置事務管理器transactionManager,並定義事務管理模板transactionTemplate,將事物管理器transactionManager注入到事物管理模板,方便將那些操作放在事務管理模板中。
<!-- 配置事物管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource"ref="dataSource"/> </bean> <!-- 定義事物管理的模板:Spring為了簡化事務管理的程式碼而提供的類 --> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager"ref="transactionManager"></property> </bean>
2.2 業務層注入程式設計式事務管理模板
在AccountService中注入TransactionTemplate事物模板(TransactionTemplate依賴DataSourceTransactionManager,DataSourceTransactionmanager依賴DataSource構造)
public class AccountServiceImpl implements AccountService { //注入轉賬的Dao private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } //注入事務管理模板 private TransactionTemplate transactionTemplate; public void setTransactionTemplate(TransactionTemplatetransactionTemplate) { this.transactionTemplate = transactionTemplate; } /** * 程式設計式事務管理 * 將操作繫結在一起,如果遇到異常,則發生事務回滾 * final型變數,內部使用 * @param out * @param in * @param money */ public void transfer(final String out,final String in,final Double money) { //在事務模板中執行操作 transactionTemplate.execute(new TransactionCallbackWithoutResult(){ @Override protected void doInTransactionWithoutResult(TransactionStatustransactionstatus){ accountDao.outMoney(out, money); int i=1/0; accountDao.inMoney(in, money); } }); } }
測試類不變,和1.5.3的測試類一樣,執行測試類,通過嘿咻你配置檔案載入Service物件,執行的Service物件的轉賬操作被放在事務管理模板當中,如果出現異常,則回滾到事物的起初裝態。
程式設計式事務管理需要手動改寫程式碼,不便操作,在開發過程中不常用,常常用到的另一種事務管理方式:宣告式事務管理。
3 宣告式事務管理
宣告式事務管理是基於AOP思想完成的,類似與在給業務層加入切面,對事務操作前後進行一定的事務管理控制。
3.1 宣告式事務管理實現方式一:基於TransactionProxyFactoryBean的方式--很少使用
這種方式很少使用,因為為每個進行事務管理的類配置一個TransactionProxyFactoryBean進行增強,多個類需要配置多個增強類,這樣導致配置和管理太麻煩,所以很少使用。
(原始碼見spring_transaction下的demo2)
(1)引入jar包:
- spring-aop-3.2.0.RELEASE.jar
- com.springsource.org.aopalliance-1.0.0.jar
(2)配置核心配置檔案applicationContext.xml
在1.4.3 核心配置檔案applicationContext.xml裡配置事務管理器,並配置業務層增強類,即業務層代理類:
<!-- 配置事物管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource"ref="dataSource"></property> </bean> <!-- 配置業務層代理 --> <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:類中的方法名稱 PROPAGATION:事務的傳播行為 ISOLATION:事務的隔離級別 readOnly:只讀(不可以進行修改操作) -Exception:發生哪些異常回滾事務 +Exception:發生哪些異常不回滾事務 <propkey="insert*">PROPAGATION_REQUIRED</prop> <propkey="update*">PROPAGATION_REQUIRED</prop> <propkey="*">PROPAGATION_REQUIRED,readOnly</prop> --> <!-- PROPAGATION_REQUIRED:事務傳播行為,表示將兩種操作放在同一個事務中。 -java.lang.ArithmeticException:出現此異常發生回滾 --> <prop key="transfer">PROPAGATION_REQUIRED,-java.lang.ArithmeticException</prop> </props> </property> </bean>
(3) Dao類、Service類和測試類demo2和1.5節的一般轉賬案例的類一樣。
通過配置Service代理類來實現事務管理,可以將兩種操作繫結在同一個事務當中,如果出現異常,則可以回滾到事物執行起初裝態。也可以通過屬性-Exception或者+Exception設定指定哪些異常回滾,哪些異常不會滾。
3.2 宣告式事務管理實現方式二:基於AspectJ的XML配置的檔案--經常使用
(原始碼見spring_transaction下的demo3)
這種基於AspectJ的切面事務管理方式避免了為每個業務類配置要給加強的代理類。
(1)引入jar包
- spring-aspects-3.2.0.RELEASE.jar
- com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
(2)引入tx/aop空間約束
(3)applicationContext3.xml配置
在1.4.3 核心配置檔案applicationContext.xml裡配置事務管理器、增強類、aop相應配置(事務通知和事務切面)
<!-- 1.配置事物管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2.配置事務通知:(事務的增強) -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--
propagation:事務傳播行為
isolation:事務隔離級別
read-only:只讀
rollback-for:發生哪些異常回滾
no-rollback-for:發生哪些異常不會滾
timeout:過期資訊
-->
<tx:method name="transfer"propagation="REQUIRED" isolation="DEFAULT"read-only="false"/>
</tx:attributes>
</tx:advice>
<!-- 3.配置切面 -->
<aop:config>
<!-- 配置切入點 -->
<aop:pointcut expression="execution(*cn.terence.spring.demo3.AccountService+.*(..))" id="pointcut1"/>
<!-- 配置切面 -->
<aop:advisor advice-ref="txAdvice"pointcut-ref="pointcut1"/> </aop:config>
(4)Dao類、Service類和測試類demo2和1.5節的一般轉賬案例的類一樣。
通過配置Service代理類來實現事務管理,可以將兩種操作繫結在同一個事務當中,如果出現異常,則可以回滾到事物執行起初裝態。也可以通過屬性rollback-for或者no-rollback-for設定指定哪些異常回滾,哪些異常不會滾。
3.3 宣告式事務管理實現方式三:基於註解的配置方式(經常使用)
(原始碼見spring_transaction下的demo4)
(1)在業務層實現類上加註解@Transactional,Service類和測試類不變,和1.5節中的Service類和測試類一樣
/** * @Transactional註解中的屬性 * propagation:事務的傳播行為 * isolation:事務的隔離級別 * readOnly:只讀 * rollbackFor:發生哪些異常事務回滾 * noRollbackFor:發生哪些異常事務不回滾 * */ @Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=false) public class AccountServiceImpl implements AccountService { //注入轉賬的Dao private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } /** * @param out * @param in * @param money */ public void transfer(String out,String in,Double money) { accountDao.outMoney(out, money); int i=1/0; accountDao.inMoney(in, money); } }
(2)配置核心配置檔案applicationContext.xml
在1.4.3 核心配置檔案applicationContext.xml裡注入事務管理器和開啟註解事務實現註解驅動。
<!-- 1.配置事物管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource"ref="dataSource"></property> </bean> <!-- 2.開啟註解事務 ,實現註解驅動--> <tx:annotation-driven transaction-manager="transactionManager"/>
(3)執行測試類
執行測試類可以實現事務的回滾,保證資料的完整一致。這種方式在開發過程中常常用到,配置簡單,不用修改程式碼。
附:
雖然 @Transactional 註解可以作用於介面、介面方法、類以及類方法上,但是 Spring 建議不要在介面或者介面方法上使用該註解,因為這隻有在使用基於介面的代理時它才會生效。另外, @Transactional 註解應該只被應用到 public 方法上,這是由 Spring AOP 的本質決定的。如果你在 protected、private 或者預設可見性的方法上使用 @Transactional 註解,這將被忽略,也不會丟擲任何異常。