1. 程式人生 > >Spring(九)----Spring的事務管理

Spring(九)----Spring的事務管理

一、事務的回顧

1. 事務:指的是邏輯上一組操作,組成這個事務的各個執行單元,要麼一起成功,要麼一起失敗!
2. 事務的特性
* 原子性
* 一致性
* 隔離性
* 永續性
3. 如果不考慮隔離性,引發安全性問題
* 讀問題:
* 髒讀:
* 不可重複讀:
* 虛讀:
* 寫問題:
* 丟失更新:
4. 如何解決安全性問題
* 讀問題解決,設定資料庫隔離級別
* 寫問題解決可以使用 悲觀鎖和樂觀鎖的方式解決

二、Spring框架的事務管理相關的類和API

要通過spring來完成事務的管理,我們需要使用spring提供的一些類和一些方法。

1. PlatformTransactionManager介面

平臺事務管理器(真正管理事務的類)。該介面有具體的實現類,根據不同的持久層框架,需要選擇不同的實現類!

spring就用這個介面來管理事務。

我們掌握兩個實現類就好了。

DataSourceTransactionManager和HibernateTransactionManager

如果,我們使用的是jdbcTemplate就要使用DataSourceTransactionManager;如果,使用了hibernate,就要使用HibernateTransactionManager。

2. TransactionDefinition介面

事務定義資訊(事務的隔離級別,傳播行為,超時,只讀)。

3. TransactionStatus介面

事務的狀態

4. 總結

上述物件之間的關係:平臺事務管理器真正管理事務物件。根據事務定義的資訊TransactionDefinition 進行事務管理,在管理事務中產生一些狀態。將狀態記錄到TransactionStatus中
5. PlatformTransactionManager介面中實現類和常用的方法


5.1. 介面的實現類
* 如果使用的Spring的JDBC模板或者MyBatis框架,需要選擇DataSourceTransactionManager實現類
* 如果使用的是Hibernate的框架,需要選擇HibernateTransactionManager實現類
5.2. 該介面的常用方法
* void commit(TransactionStatus status) 
* TransactionStatus getTransaction(TransactionDefinition definition) 
* void rollback(TransactionStatus status) 

6. TransactionDefinition

6.1. 事務隔離級別的常量
* static int ISOLATION_DEFAULT -- 採用資料庫的預設隔離級別
* static int ISOLATION_READ_UNCOMMITTED 
* static int ISOLATION_READ_COMMITTED 
* static int ISOLATION_REPEATABLE_READ 
* static int ISOLATION_SERIALIZABLE 
6.2. 事務的傳播行為常量(不用設定,使用預設值)
* 先解釋什麼是事務的傳播行為:解決的是業務層之間的方法呼叫!!
* PROPAGATION_REQUIRED(預設值)-- A中有事務,使用A中的事務。如果沒有,B就會開啟一個新的事務,將A包含進來。(保證A,B在同一個事務中),預設值!!
* PROPAGATION_SUPPORTS-- A中有事務,使用A中的事務。如果A中沒有事務。那麼B也不使用事務。
* PROPAGATION_MANDATORY-- A中有事務,使用A中的事務。如果A沒有事務。丟擲異常。
* PROPAGATION_REQUIRES_NEW(記)-- A中有事務,將A中的事務掛起。B建立一個新的事務(保證A,B沒有在一個事務中)。
* PROPAGATION_NOT_SUPPORTED-- A中有事務,將A中的事務掛起。
* PROPAGATION_NEVER -- A中有事務,丟擲異常。
* PROPAGATION_NESTED(記)-- 巢狀事務。當A執行之後,就會在這個位置設定一個儲存點。如果B沒有問題。執行通過。如果B出現異常,執行客戶根據需求回滾(選擇回滾到儲存點或者是最初始狀態

三、搭建事務管理轉賬案例的環境

強調:簡化開發,以後DAO可以繼承JdbcDaoSupport類

1. 步驟一:建立WEB工程,引入需要的jar包
* IOC的6個包
* AOP的4個包
* C3P0的1個包
* MySQL的驅動包
* JDBC目標2個包
* 整合JUnit測試包

2. 步驟二:引入配置檔案
* 引入log4j.properties
* 引入applicationContext.xml

<!-- 配置C3P0的連線池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
	<property name="driverClass" value="com.mysql.jdbc.Driver" />
	<property name="jdbcUrl" value="jdbc:mysql:///spring_day03" />
	<property name="user" value="root" />
	<property name="password" value="123456" />
</bean>

<!-- 配置jdbc的模板類 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
	<property name="dataSource" ref="dataSource" />
</bean>
3. 步驟三:建立對應的包結構和類
4. 步驟四:引入Spring的配置檔案,將類配置到Spring中
<!-- 配置業務層和持久層 -->
<bean id="accountDao" class="com.ken.demo1.AccountDaoImpl" />

<bean id="accountService" class="com.ken.demo1.AccountServiceImpl">
	<property name="accountDao" ref="accountDao" />
</bean>
5. 步驟五:在業務層注入DAO ,在DAO中注入JDBC模板(強調:簡化開發,以後DAO可以繼承JdbcDaoSupport類)

dao層實現類:

public class AccountDaoImpl implements AccountDao {

	private JdbcTemplate jdbcTemplate;

	public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}

	/**
	 * 扣錢
	 * 
	 * @param out
	 * @param money
	 */
	@Override
	public void outMoney(String out, double money) {
		jdbcTemplate.update("update t_account set money = money - ? where name = ? ", money, out)
	}

	/**
	 * 加錢
	 * 
	 * @param in
	 * @param money
	 */
	@Override
	public void inMoney(String in, double money) {
		jdbcTemplate.update("update t_account set money = money + ? where name = ? ", money, in)
	}

}
配置:
<!-- 配置業務層和持久層 -->
<bean id="accountDao" class="com.ken.demo1.AccountDaoImpl">
	<property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>

<bean id="accountService" class="com.ken.demo1.AccountServiceImpl">
	<property name="accountDao" ref="accountDao" />
</bean>
但是,如果,我們在每個dao的實現類裡面都要一一去配置jdbcTemplate,就很麻煩了。

我們可以給每個dao的實現類繼承一個父類,然後,父類中有注入jdbcTemplate的這段程式碼,也就是下圖的程式碼


繼承父類之後,如下:

public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
	/**
	 * 扣錢
	 * 
	 * @param out
	 * @param money
	 */
	@Override
	public void outMoney(String out, double money) {
		this.getJdbcTemplate().update(" update t_account set money = money - ? where name = ? ", money, out);
	}

	/**
	 * 加錢
	 * 
	 * @param in
	 * @param money
	 */
	@Override
	public void inMoney(String in, double money) {
		this.getJdbcTemplate().update(" update t_account set money = money + ? where name = ? ", money, in);
	}
}
我們看一下父類的原始碼:



並且,這個方法是final的。說明子類不能重寫這個方法。

那麼,子類如何使用父類提供的jdbcTemplate呢?父類提供了get方法。


我們在分析一段父類的原始碼:


這個意思就是說,如果,這個dao沒有配置jdbcTemplate但配置了dataSource,那麼這個類會用我們配置的dataSrouce去幫我們建立一個jdbcTemplate。這樣一來,我們可以修改一下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: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">

	<!-- 配置C3P0的連線池 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass" value="com.mysql.jdbc.Driver" />
		<property name="jdbcUrl" value="jdbc:mysql:///spring_day03" />
		<property name="user" value="root" />
		<property name="password" value="123456" />
	</bean>

	<!-- 配置jdbc的模板類 -->
	<!-- <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource" />
	</bean> -->

	<!-- 配置業務層和持久層 -->
	<bean id="accountDao" class="com.ken.demo1.AccountDaoImpl">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<bean id="accountService" class="com.ken.demo1.AccountServiceImpl">
		<property name="accountDao" ref="accountDao" />
	</bean>
</beans>

原始碼下載

我們在service的實現類中新增一個異常:


再執行測試類,結果是,outMoney()方法執行了,inMoney()方法沒執行。這就引出一個問題,我們要通過事務來控制業務層的操作。

四、spring的事務管理的分類

spring有兩種方式來管理事務:程式設計式事務管理、宣告式事務管理

不管是哪種方式,都用平臺事務管理器(PlatformTransactionManager)

4.1 spring程式設計式事務管理(瞭解)

正常jdbc連線資料庫,就是通過connection,只有connection才能操作事務。這是最底層。


步驟一:配置一個事務管理器,Spring使用PlatformTransactionManager介面來管理事務,所以咱們需要使用到他的實現類!

<!-- 配置平臺事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource" />
</bean>
Spring為了簡化事務管理的程式碼:提供了模板類 TransactionTemplate,所以手動程式設計的方式來管理事務,只需要使用該模板類即可。

步驟二:配置事務管理的模板

<!-- 手動編碼,提供了模板類,使用該類管理事務比較簡單 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
	<property name="transactionManager" ref="transactionManager" />
</bean>

步驟三:在需要進行事務管理的類中,注入事務管理的模板
<bean id="accountService" class="com.ken.demo1.AccountServiceImpl">
	<property name="accountDao" ref="accountDao" />
	<property name="transactionTemplate" ref="transactionTemplate" />
</bean>

步驟四:在業務層使用模板管理事務

// 注入事務模板物件
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
	this.transactionTemplate = transactionTemplate;
}
public void pay(final String out, final String in, final double money) {

	transactionTemplate.execute(new TransactionCallbackWithoutResult() {
		//事務的執行,如果沒有問題,提交。如果出現異常,回滾。
		@Override
		protected void doInTransactionWithoutResult(TransactionStatus arg0) {
			// 先扣錢
			accountDao.outMoney(out, money);
			int a = 10 / 0;
			// 再加錢
			accountDao.inMoney(in, money);
		}
	});
}

執行測試方法,有異常,但是,錢不轉。

4.2 宣告式事務

Spring框架的事務管理之宣告式事務管理,即通過配置檔案來完成事務管理(AOP思想)。

宣告式事務管理又分成兩種方式:基於AspectJ的XML方式(重點掌握)和基於AspectJ的註解方式(重點掌握)

4.2.1 基於AspectJ的XML方式

我們把專案的包和配置檔案複製出來一份:




事務管理的底層基於aop。不改業務層的程式碼,aop幫我們加事務。aop需要寫切面類,切面裡面需要些通知,然後再配置一下。以前,我們做demo的時候“記錄日誌”需要自己寫。現在,spring的事務管理已經幫我們寫好了事務管理程式碼,切面類不用我們編寫了,我們只要配置一下。spring的事務的程式碼已經寫好了,類已經準備好了。我們得把它配好。

步驟一:恢復轉賬開發環境

步驟二:引入AOP的開發包

步驟三:配置事務管理器

<!-- 配置平臺事務管理器 -->
<bean id="transactionManager"
	class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource" />
</bean>
步驟四:配置事務增強
<!-- 宣告式事務(採用XML配置檔案的方式) -->
<!-- 先配置通知 -->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
	<tx:attributes>
		<!--
			name		:繫結事務的方法名,可以使用萬用字元,可以配置多個。
			propagation	:傳播行為
			isolation	:隔離級別
			read-only	:是否只讀
			timeout		:超時資訊
			rollback-for:發生哪些異常回滾.
			no-rollback-for:發生哪些異常不回滾.
		 -->
		<!-- 哪些方法加事務 -->
		<tx:method name="pay" propagation="REQUIRED"/>
	</tx:attributes>
</tx:advice>
步驟五:配置AOP的切面
<!-- 配置aop 如果是自己編寫的aop,使用aop:aspect配置;如果,使用的是spring框架提供的通知,使用aop:advisor-->
<aop:config>
	<!-- aop:advisor,是spring框架提供的通知 -->
	<aop:advisor advice-ref="myAdvice" pointcut="execution(public * com.ken.demo2.AccountServiceImpl.pay(..))"/>
</aop:config>
步驟六:編寫測試類

見原始碼

4.2.2 基於AspectJ的註解方式

重點掌握,最簡單的方式

步驟一:恢復轉賬的開發環境

步驟二:配置事務管理器

<!-- 配置平臺事務管理器 -->
<bean id="transactionManager"
	class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource" />
</bean>
步驟三:開啟註解事務
<!-- 開啟註解事務 -->
<tx:annotation-driven transaction-manager="transactionManager" />
步驟四:在業務層上新增一個註解[email protected]
在類上添加註解,類中的所有的方法全部都有事務;
也可以只在某個方法上加。

步驟五:編寫測試類

見原始碼

原始碼