Spring學習筆記-事務管理
Spring支持兩種事務管理的方式:
編程式的事務管理,在實際應用中很少使用,通過TransactionTemplate手動管理事務。
聲明式的事務管理,使用XML配置聲明,開發中推薦使用(代碼侵入性最小),Spring的聲明式事務是通過AOP實現的。
什麼是事務
事務指的是邏輯上的一組操作,這組操作要麼全都成功,要麼全都失敗。最典型的例子就是銀行轉賬的問題。
事務的特性:
原子性:事務是不可分割的工作單位
一致性:事務前後數據的完整性必須保持一致
隔離性:多個併發事務數據要相互隔離
持久性:事務一旦提交,它對數據庫的改變是永久性的
Spring事務接口介紹
Spring事務管理高層抽象接口主要包括3個接口:
1、PlatformTransactionManager事務管理器
Spring爲不同的持久層框架提供了不同的PlatformTransactionManager接口實現。如JDBC、Mybatis使用DataSourceTransactionManager,Hibernate用HibernateTransactionManager等。
2、TransactionDefinition事務定義信息
包含(隔離級別、傳播行爲、超時、是否只讀)等一些信息。
若不考慮隔離性,將會造成髒讀、不可重複讀、幻讀等。
事務的傳播行爲主要解決service層裏方法的相互調用,解決事務是如何傳遞的。
3、TransactionStatus事務具體運行狀態
事務隔離級別:
事務的傳播行爲:
測試環境搭建
applicationContext.xml
<context:annotation-config/> <context:component-scan base-package="com"></context:component-scan> <!--引入配置文件--> <!--1.配置數據庫相關參數–>--> <context:property-placeholder location="classpath:jdbc.properties"/> <!--2.數據庫連接池--> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--配置連接池屬性--> <property name="driverClass" value="${jdbc_driver}" /> <!-- 基本屬性 url、user、password --> <property name="jdbcUrl" value="${jdbc_url}" /> <property name="user" value="${jdbc_username}" /> <property name="password" value="${jdbc_password}" /> <!--c3p0私有屬性--> <property name="maxPoolSize" value="30"/> <property name="minPoolSize" value="10"/> <!--關閉連接後不自動commit--> <property name="autoCommitOnClose" value="false"/> <!--獲取連接超時時間--> <property name="checkoutTimeout" value="1000"/> <!--當獲取連接失敗重試次數--> <property name="acquireRetryAttempts" value="2"/> </bean>
Dao層
@Repository public interface AccountDao { void outMoney(String out, Double money); void inMoney(String in, Double money); }
@Repository("AccountDao") public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { @Autowired private ComboPooledDataSource dataSource; /**解決 * java.lang.IllegalArgumentException: * 'dataSource' or 'jdbcTemplate' is required * 的問題。 * 繼承了JdbcDaoSupport注入dataSource出現null的情況 */ @PostConstruct private void initialize() { setDataSource(dataSource); } public void outMoney(String out, Double money) { String sql = "update test set money = money - ? where name = ?"; this.getJdbcTemplate().update(sql, money, out); } public void inMoney(String in, Double money) { String sql = "update test set money = money + ? where name = ?"; this.getJdbcTemplate().update(sql, money, in); } }
Service層
@Service public interface AccountService { void transfer(String out, String in, Double money); }
@Service("AccountService") public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; public void transfer(String out, String in, Double money) { System.out.println(out+"向"+in+"轉賬"+money); accountDao.outMoney(out,money); //出現異常 //若沒有事務處理,這時out賬戶錢轉出,但是in賬戶錢未轉入 int i = 1/0; accountDao.inMoney(in,money); } }
Main.java
在未進行事務處理時,會出現aaa中錢減少,bbb中錢未增加的情況。
public class Main { public static void main(String[] args){ ApplicationContext context = new ClassPathXmlApplicationContext( "applicationContext.xml"); AccountService accountService = (AccountService) context.getBean("AccountService"); accountService.transfer("aaa","bbb",100.0); } }
編程式事務控制
Spring提供了事務管理的模板TransactionTemplate,在哪裏使用事務就在哪裏注入這個模板。
在applicationContext.xml文件中添加如下代碼,配置事務管理器和模板。
<!--配置事務管理器--> <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"/> </bean>
編寫AccountServiceImpl2.java,進行編程式事務處理
@Service("AccountService2") public class AccountServiceImpl2 implements AccountService { @Autowired private AccountDao accountDao; //注入事務管理模板 @Autowired private TransactionTemplate transactionTemplate; //使用匿名內部類,傳入的參數需要final public void transfer(final String out, final String in, final Double money) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) { //使用匿名內部類實現編程式事務的處理 System.out.println(out + "向" + in + "轉賬" + money); accountDao.outMoney(out, money); //出現異常 //若沒有事務處理,這時out賬戶錢轉出,但是in賬戶錢未轉入 int i = 1 / 0; accountDao.inMoney(in, money); } }); } }
測試
AccountService accountService2 = (AccountService) context.getBean("AccountService2"); accountService2.transfer("aaa","bbb",100.0);
出現異常時不會出現aaa中錢減少而bbb中錢未增加的情況,出現異常進行了事務的一個回滾。
這種方式修改代碼較多,不推薦這種方式,推薦使用下面的聲明式事務的方式。
聲明式事務控制
基於AOP思想進行處理。
方法一,基於TransactionProxyFactoryBean
配置applicationContext.xml,添加TransactionProxyFactoryBean的相關配置。
這裏是通過代理實現功能的增強,基於AOP攔截規則。
這裏說明一下prop的格式 :
PROPAGATION,ISOLATION,readOnly,-Exception,+Exception分別代表
傳播行爲(7種),隔離級別,只讀事務,發生這些異常回滾,發生這些異常提交事務。
<!--聲明式事務--> <!--方法一:基於TransactionProxyFactoryBean--> <!--配置業務層代理--> <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="insert*">PROPAGATION_REQUIRED</prop> <prop key="update*">PROPAGATION_REQUIRED</prop> <!--這裏配置對應的方法進行AOP攔截,*攔截所有,這裏測試transfer方法--> <!--設置隔離級別--> <prop key="transfer">PROPAGATION_REQUIRED</prop> <prop key="*">PROPAGATION_REQUIRED,readOnly</prop> </props> </property> </bean>
AccountServiceImpl不用修改任何代碼,在測試類中獲得的bean是代理類的對象。
AccountService accountService3 = (AccountService) context.getBean("accountServiceProxy"); accountService3.transfer("aaa","bbb",100.0);
方法二,使用XML配置聲明事務(原始方式)
配置applicationContext.xml,添加聲明事務。
<!--方法二:使用XML配置聲明事務(原始方式)--> <!--配置事務的通知:(事務的增強)--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!--下面屬性分別爲傳播行爲,隔離級別,只讀,發生這些異常回滾,發生這些異常提交事務--> <tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT" read-only="false" rollback-for="" no-rollback-for=""/> </tx:attributes> </tx:advice> <!--配置切面--> <aop:config> <!--切入點配置--> <aop:pointcut id="pointcut1" expression="execution(* com.service.AccountService+.*(..))"/> <!--配置切面--> <!--在pointcut1這個切入點上使用txAdvice這個增強--> <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/> </aop:config>
也是基於AOP攔截規則,其他不用修改任何代碼,在測試時直接調用原來的方法。
AccountService accountService = (AccountService) context.getBean("AccountService"); accountService.transfer("aaa","bbb",100.0);
方法三,使用註解
配置applicationContext.xml,添加基於註解的聲明事務
<!--方法三:配置基於註解的聲明式事務--> <tx:annotation-driven transaction-manager="transactionManager"/>
然後在需要進行事務處理的類或方法上使用@Transactional註解
//同樣有傳播行爲,隔離級別等屬性進行設置 @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT) public void transfer(String out, String in, Double money) { System.out.println(out+"向"+in+"轉賬"+money); accountDao.outMoney(out,money); //出現異常 //若沒有事務處理,這時out賬戶錢轉出,但是in賬戶錢未轉入 int i = 1/0; accountDao.inMoney(in,money); }
總結
編程式事務管理:手動編寫代碼,很少使用
聲明式事務管理:
l 方法一:基於TransactionProxyFactoryBean方式,需要爲每個進行事務管理的類配置一個TransactionProxyFactoryBean進行增強,很少使用。
l 方法二:基於AspectJ的XML方式,一旦配置好,類上不需要添加任何東西,經常使用。
l 方法三:基於註解方式,配置簡單,需要在業務層上添加@Transactional註解,經常使用。