1. 程式人生 > >看了慕課的spring事務管理,然後整理了一下,以便以後自己學習

看了慕課的spring事務管理,然後整理了一下,以便以後自己學習

事務的特性:
    1. 原子性.
        事務是一個不可分割的整體,要麼都失敗,要麼都成功
    2. 一致性:
        事務前後性必須資料的完整性必須保持一致
    3. 隔離性:
        多個使用者併發訪問資料庫的時候,一個使用者的事務不能被其他使用者的事務給干擾,
        多個併發事務之間資料要相互隔離
    4. 永續性:
        永續性就是事務一旦提交,那麼對於資料庫中就是永久性的,不會因為資料庫傳送事故而改變
事務管理的三個主要介面
    platformTransactionManager
        事務管理器

        spring為不同的持久層框架提供了不同的介面
            對於 mybatis,jdbc 使用的是 org.springframework.jdbc.datasource.DataSourceTransaction
            對於 hibernate           org.springframework.orm.hibernate3.HibernateTransactionManager
            對於 jpa           org.springframework.orm.jpa.JpaTransactionManager
            對於 jdo           org.springframework.orm.jdo.JdoTransactionManager
            對於 jta(是一個分散式事務) org.springframework.orm.jta.JtaTransactionManager
    TransactionDefinition
        事務定義資訊 (隔離,傳播,超時,只讀)
    TransactionStatus
        事務具體執行狀態
事務中出現的問題:
    1. 髒讀:
        一個事務讀到了另一個事務改寫但還未提交的資料,如果這些資料回滾,則讀到的資料是無效的
    2. 不可重複讀:
        在同一事務中,多次讀取同一資料返回的結果有所不同
    3. 幻讀:
        在一個事務中讀取了幾行資料,當另一個事務插入了幾條資料後,在後來的查詢後,發現一些沒有的資料.
事務的隔離級別:
    DEFAULT        使用後端資料庫預設的隔離級別
            對於mysql 預設的隔離級別(REPEATABLE_READ)
                oracle  (READ_COMMITED)
    
    READ_UNCOMMITED    允許讀取修改還未提交的資料,可能會導致,髒,不可重複,幻讀的情況
    
    READ_COMMITED    允許在事務提交以後讀取,但不可避免不可重複,幻讀的情況
    
    REPEATABLE_READ 對於相同欄位多次讀取時一致的,除非資料被事務本身改變,可防止髒讀,不可重複..但幻讀不行
    
    SERIALIZABLE    完全服從ACID的隔離級別,確保不發生髒,幻,不可重複讀,這是所有隔離級別中最慢的,它是完全
            鎖在事務中涉及的資料表來完成的.



事務的傳播行為:
    當一個功能  需要呼叫 2個業務層的方法 才能完成, 每個service層的方法都有事務.\

    PROPAGATION_REQUIRED *    支援當前事務,如果不存在就新建一個

    PROPAGATION_SUPPORTS    支援當前事務,如果不存在就不適用事務,
    
    PROPAGATION_MANDATORY    支援當前事務,如果不存在,丟擲異常
    
    PROPAGATION_REQUIRED_NEW * 如果有事務存在,掛起當前事務,新建一個
    
    PROPAGATION_NOT_SUPPORTS 以非事務方式執行,如果事務存在,掛起當前事務

    PROPAGATION_NEVER    以非事務方式執行,丟擲異常

    PROPAGATION_NESTED *    如果當前事務存在咋巢狀事務執行

spring 支援兩種事務管理
    1. 程式設計式的事務管理
     1. 在實際應用中很少使用
     2. 通過TransactionTemplate手動管理事務
    使用XML配置宣告式事務
     1. 開發中推薦使用(程式碼侵入最小)

     2. Spring的宣告式事務式通過AOP實現的

            1. 基於 TransactionProxyFactoryBean (很少使用)
        2. 基於 AspectJ的XML方式 (經常使用)
            一旦配置好了,不需要在新增任何東西
        3. 基於 註解 (經常使用)
            配置簡單,需要在每個業務層類上面加 @Transcational 註解


做的一個例項 是   轉賬問題   a 轉 b  兩個人的賬戶相應增加,減少,當發生異常,則事務不進行提交,a,b的金額沒有發生改變

在 SpringTest 測試類中 需要加入
    @RunWith(SpringJUnit4ClassRunner.class) // //使用junit4進行測試  
    @ContextConfiguration("classpath:applicationContext.xml") // 載入配置檔案
    這兩個註解


 搭建測試環境

    userDao,userDaoImpl

import org.springframework.jdbc.core.support.JdbcDaoSupport;

public class UserDaoImpl extends JdbcDaoSupport  implements UserDao{

	
	@Override
	public void outPrice(String name, double price) {
		// TODO Auto-generated method stub
		String sql = "update user set price = price - ? where username = ?";
		this.getJdbcTemplate().update(sql,price,name);
	}

	@Override
	public void intPrice(String name, double price) {
		// TODO Auto-generated method stub
		String sql = "update user set price = price + ? where username = ?";
		this.getJdbcTemplate().update(sql,price,name);
	}
	
}

public interface UserDao {
	/**
	 * 轉賬
	 */
	public void outPrice(String name,double price);
	
	/**
	 * 收賬
	 */
	public void intPrice(String name,double price);
}

userService,userServiceImpl

public interface UserService {
	public void out(String name,String name2,double price);
}
import javax.annotation.Resource;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

public class UserServiceImpl implements UserService{

	@Resource(name="userDao")
	public UserDao userDao;
	
	public void setUserDao(UserDao userDao) {
		this.userDao = userDao;
	}
	
	// 事務模板
	private TransactionTemplate transactionTemplate;
	public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
		this.transactionTemplate = transactionTemplate;
	}

	@Override
	public void out(String name, String name2, double price) {
		// TODO Auto-generated method stub
		transactionTemplate.execute(new TransactionCallbackWithoutResult() {
			@Override
			protected void doInTransactionWithoutResult(TransactionStatus tr) {
				// TODO Auto-generated method stub
				userDao.intPrice(name, price);
				int a = 1 / 0;
				userDao.outPrice(name2, price);
			}
		}
		);
	}

}


1. 程式設計式的事務管理

    在 service 中使用TransactionTemplate
    TransactionTemplate 依賴 DataSourceTransactionManager
    DataSourceTransactionManager依賴DataSource構造

    1.1 在 applicationContext 中配置事務管理器

<?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:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/tx 
       http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
       http://www.springframework.org/schema/aop 
       http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.0.xsd"
       >
    <!-- 資料庫配置檔案 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>
	<!-- 建立資料來源 -->
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" >
		<property name="driverClassName" value="${driver}"></property>
		<property name="url" value="${url}"></property>
		<property name="username" value="${name}"></property>
		<property name="password" value="${password}"></property>
		<property name="maxActive" value="10"></property>
		<property name="maxIdle" value="5"></property>
	</bean>       
	 
	<!-- 配置資料層 -->
	<bean id="userDao" class="com.zyh.UserDaoImpl" scope="prototype">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<!-- 配置業務層 -->
	<bean id="userService" class="com.zyh.UserServiceImpl" scope="prototype">
		<property name="userDao" ref="userDao"></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>

1.2  在 service 實現類中注入 transactionTemplate 模板來實現事務管理

// 當在service 方法中注入了transactionTemplate 那麼在 applicationContext 中相應的 service 
		//	也注入transactionTemplate
		private TransactionTemplate transactionTemplate;

		public void setTransactionTemplate(TransactionTemplate transactionTemplate){
			this.transactionTemplate = transactionTemplate;
		}

		然後呼叫 transactionTemplate 中的方法 execute 
		此方法呢需要一個匿名內部類  new TransactionCallbackWithoutResult(){
				@Override
			protected void doInTransactionWithoutResult(TransactionStatus tr) {
				// TODO Auto-generated method stub
				userDao.intPrice(name, price);
				int a = 1 / 0;
				userDao.outPrice(name2, price);
			}
		}

1.3 然後進行測試

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTest {

	@Resource(name="userService")
	public UserService userService;
	

	public void setUserService(UserService userService) {
		this.userService = userService;
	}

	@Test
	public void springDemo() {
		userService.out("aaa", "bbb", 200);
	}
}

然後檢視資料庫,發現中間發生異常,事務是不會提交的


2. spring宣告式的事務管理

    宣告式的事務管理是基於spring aop思想的

     將環境恢復到 一開始狀態,(將所有的關於 transactionTemplate 的都刪除)

1. 配置事務管理
   <!-- 建立事務管理 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- 將資料連線注入過來 -->
		<property name="dataSource" ref="dataSource"></property>
	</bean>
2. 配置代理
<!-- 配置 代理 -->
	<bean  id="userServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
		<!-- 配置目標物件 -->
		<property name="target" ref="userService"></property>
		<!-- 注入事務管理 -->
		<property name="transactionManager" ref="transactionManager"></property>
		<!-- 注入事務屬性 -->
		<property name="transactionAttributes">
			<props>
				<!-- 
					prop格式:
						PROPAGATION    : 事務的傳播行為
						ISOLATION      : 事務的隔離級別
						readOnly	   : 只讀
						-Exception	   : 發生哪些異常回滾事務
						+Exception	   : 發生那修異常事務不會滾
						
				 -->
				<prop key="out">PROPAGATION_REQUIRED</prop>
			</props>
		</property>
	</bean>
然後service層不需要再進行修改了,只需要修改SpringTest

    將userService的 注入換為 userServiceProxy

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 SpringTest {

	/**
	 * 注入代理類
	 */
	
//	@Resource(name="userService")
	@Resource(name="userServiceProxy")
	public UserService userService;
	

	@Test
	public void springDemo() {
		userService.out("aaa", "bbb", 200);
	}
}

然後進行測試.成功,   當有異常的時候是不會進行 事務提交,沒有異常正常提交事務.

然後在 配置檔案 bean userServiceProxy 的 事務屬性中

<!-- 注入事務屬性 -->
		<property name="transactionAttributes">
			<props>
				<!-- 
					prop格式:
						PROPAGATION    : 事務的傳播行為
						ISOLATION      : 事務的隔離級別
						readOnly	   : 只讀
						-Exception	   : 發生哪些異常回滾事務
						+Exception	   : 發生那修異常事務不會滾
						
				 -->
				<prop key="out">PROPAGATION_REQUIRED,readOnly</prop>
			</props>
		</property>

多配置一個readonly 然後進行測試會 報異常


因為進行的是修改操作.所有當設定為只讀的話 就會報異常


上面那個是第一種方式 基於 TransactionProxyFactoryBean的原始方式
    
    缺點 只能對一個業務層類 管理,當有多個模組的話就需要配置多個Proxy來實現事務管理
    宣告式事務管理,第二種方式As

    在 applicationConext 中

    

<!-- 配置事務的通知 -->
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<!-- 
				propagation	: 事務傳播行為
				isolation	: 事務隔離級別
				readOnly	: 只讀
				rollback-for : 發生哪些異常回滾
				no-rollback-for: 發生哪些異常不回滾
				timeout	: 過期資訊
			 -->
			<tx:method name="out" propagation="REQUIRED" isolation="DEFAULT" />
		</tx:attributes>
	</tx:advice>
	
	
	<!-- 配置切面 -->
	<aop:config>
		<!-- 配置切入點 -->
		<aop:pointcut expression="execution(* zyh.UserService+.*(..))" id="pointcut1"/>
		<aop:advisor advice-ref="pointcut1" pointcut="pointcut1"/>
	</aop:config>
	
	這種方式的只需要在配置檔案配置後就可以 實現對事務管理, 代理物件在對應類生成時生成



	第三種 基於註解的 宣告式事務管理


	<!-- 開啟事務註解 -->
	<tx:annotation-driven transaction-manager="transactionManager"/>


	然後只需要在 指定的類上面新增Transactional

	/**
	 * 	propagation	: 事務傳播行為
				isolation	: 事務隔離級別
				readOnly	: 只讀
				rollback-for : 發生哪些異常回滾
				no-rollback-for: 發生哪些異常不回滾
				timeout	: 過期資訊
	 */
	@Transactional
	public class UserServiceImpl implements UserService