Spring中的事務

1)配置文件:java

1)註解的形式:
	spring + mybatis:
		說明:mybatis會自動參與到spring事務的管理中,無需額外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的數據源與DataSourceTransactionManager引用的數據源一致便可,不然事務管理不起做用。
	
		<!-- 定義事務管理器 -->  
		<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
			<property name="dataSource" ref="dataSource" />
		</bean> 
		<!-- 使用註解的形式管理事務 -->  
		<tx:annotation-driven transaction-manager="transactionManager" /> 
	
		<!-- 定義SqlSessionFactory -->
		<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
			<property name="dataSource" ref="dataSource" />
			<property name="configLocation" value="classpath:/mybatis_advertise.xml" />
		</bean>

	spring + hibernate:
		<!-- 事務管理器 -->
		<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
			<property name="sessionFactory" ref="sessionFactory" />
		</bean>
		<!-- 使用註解的形式管理事務 -->
		<tx:annotation-driven transaction-manager="transactionManager"/>

		<!-- 定義SessionFactory -->  
        <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">  
			<property name="dataSource" ref="dataSource" />
            <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        </bean>  


2)切面的形式:

	<!-- 定義事務管理器 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<!-- 事務的處理策略:至關於註解形式中的@Transactional註解 -->
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<!-- 設置事務的屬性,至關於@Transactional註解的屬性 -->
			<tx:method name="query*" read-only="true" />
			<tx:method name="save*" propagation="REQUIRED" />
			<tx:method name="*" /> 
		</tx:attributes>
	</tx:advice>

	<aop:config>
		<!--定義須要使用事務的切入點 -->
		<aop:pointcut id="myPointcut" expression="execution(* com.jxn.mybatis.service.*.*(..))" />
		<!--將事務的處理策略應用到指定的切入點 -->
		<aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut" />
	</aop:config>

2)原理:spring

1)spring的事務管理是經過spring的AOP(動態代理)來實現的。

	spring中事務生效的一個前提:調用的方法必須被TransactionInterceptor攔截。
		說明:只有經過TransactionInterceptor攔截的方法纔會被加入到spring的事務管理中。
		舉例:
			在方法A中調用@Transactional修飾的方法B時,若是方法A與方法B在同一個類TestClass中,則加在方法B上的@Transactional註解不起做用,由於此時方法A對方法B的調用是不會被攔截器攔截的!
			解決辦法:在方法A中,使用當前的代理對象來調用方法B。eg:((TestClass)AopContext.currentProxy()).B();

2)對於正常的事務管理,必須關閉數據庫的自動提交模式,spring會將JDBC Connection的自動提交特性設置爲false。

3)spring經過ThreadLocal模式,將事務中的鏈接(JDBC Connection)綁定到當前線程中。
	說明:
		1>Spring的事務基於數據庫的事務機制。
		2>要想將多個DAO方法放到一個事務中,則全部的DAO方法都必須使用同一個數據庫鏈接(Connection),不然沒法實現事務的功能。

4)源碼:
	1>事務管理器:DataSourceTransactionManager

		package org.springframework.jdbc.datasource;

		import java.sql.Connection;
		import java.sql.SQLException;
		import javax.sql.DataSource;

		import org.springframework.beans.factory.InitializingBean;
		import org.springframework.transaction.CannotCreateTransactionException;
		import org.springframework.transaction.TransactionDefinition;
		import org.springframework.transaction.TransactionSystemException;
		import org.springframework.transaction.support.AbstractPlatformTransactionManager;
		import org.springframework.transaction.support.DefaultTransactionStatus;
		import org.springframework.transaction.support.ResourceTransactionManager;
		import org.springframework.transaction.support.TransactionSynchronizationManager;

		@SuppressWarnings("serial")
		public class DataSourceTransactionManager extends AbstractPlatformTransactionManager implements ResourceTransactionManager, InitializingBean {

			private DataSource dataSource;

			/**
			 * Create a new DataSourceTransactionManager instance.
			 * A DataSource has to be set to be able to use it.
			 * @see #setDataSource
			 */
			public DataSourceTransactionManager() {
				setNestedTransactionAllowed(true);
			}

			// Create a new DataSourceTransactionManager instance.
			public DataSourceTransactionManager(DataSource dataSource) {
				this();
				setDataSource(dataSource);
				afterPropertiesSet();
			}
			
			// Set the JDBC DataSource that this instance should manage transactions for.
			public void setDataSource(DataSource dataSource) {
				if (dataSource instanceof TransactionAwareDataSourceProxy) {
					// If we got a TransactionAwareDataSourceProxy, we need to perform transactions
					// for its underlying target DataSource, else data access code won't see
					// properly exposed transactions (i.e. transactions for the target DataSource).
					this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
				}
				else {
					this.dataSource = dataSource;
				}
			}

			/**
			 * 參數:
			 * 	transaction 			當前的事務對象
			 *  TransactionDefinition	事務定義類,包含事務的傳播行爲、隔離級別等。
			 */
			@Override
			protected void doBegin(Object transaction, TransactionDefinition definition) {
				DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
				Connection con = null;

				try {
					// 當前的事務對象是否已經擁有數據庫的鏈接(Connection對象),若是沒有,則從數據源獲取一個數據庫鏈接
					if (txObject.getConnectionHolder() == null || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
						Connection newCon = this.dataSource.getConnection();
						if (logger.isDebugEnabled()) {
							logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
						}
						// 第二個參數爲true,表示該鏈接是新獲取的,尚未綁定到當前線程中。
						txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
					}

					txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
					con = txObject.getConnectionHolder().getConnection();

					// This implementation sets the isolation level but ignores the timeout.
					Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
					txObject.setPreviousIsolationLevel(previousIsolationLevel);

					// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
					// so we don't want to do it unnecessarily (for example if we've explicitly
					// configured the connection pool to set it already).
					if (con.getAutoCommit()) {
						txObject.setMustRestoreAutoCommit(true);
						if (logger.isDebugEnabled()) {
							logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
						}
						con.setAutoCommit(false); // 將Connection的自動提交設置爲false
					}
					txObject.getConnectionHolder().setTransactionActive(true);

					int timeout = determineTimeout(definition);
					if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
						txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
					}

					// 若是事務對象持有的數據庫鏈接是新建的,則將ConnectionHolder(封裝了Connection對象)和當前線程綁定;事務提交或回滾後,綁定解除。
					if (txObject.isNewConnectionHolder()) {
						TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
					}
				}
				catch (Throwable ex) {
					DataSourceUtils.releaseConnection(con, this.dataSource);
					throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
				}
			}

			@Override
			protected boolean isExistingTransaction(Object transaction) {
				DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
				return (txObject.getConnectionHolder() != null && txObject.getConnectionHolder().isTransactionActive());
			}
			
			/**
			 * Return the JDBC DataSource that this instance manages transactions for.
			 */
			public DataSource getDataSource() {
				return this.dataSource;
			}

			public void afterPropertiesSet() {
				if (getDataSource() == null) {
					throw new IllegalArgumentException("Property 'dataSource' is required");
				}
			}

			public Object getResourceFactory() {
				return getDataSource();
			}

			@Override
			protected Object doGetTransaction() {
				DataSourceTransactionObject txObject = new DataSourceTransactionObject();
				txObject.setSavepointAllowed(isNestedTransactionAllowed());
				ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(this.dataSource);
				txObject.setConnectionHolder(conHolder, false);
				return txObject;
			}
		}

	2>事務同步管理器:
	
		public abstract class TransactionSynchronizationManager {

			private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
			private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<Map<Object, Object>>("Transactional resources");
					
			// ******************** NamedThreadLocal源碼 ********************
			
				public class NamedThreadLocal<T> extends ThreadLocal<T> {

					private final String name;
					
					// Create a new NamedThreadLocal with the given name.
					public NamedThreadLocal(String name) {
						Assert.hasText(name, "Name must not be empty");
						this.name = name;
					}

					@Override
					public String toString() {
						return this.name;
					}
				}
			// **************************************************************
			
					
			/**
			 * Bind the given resource for the given key to the current thread.
			 * 
			 * 將當前線程和一個map綁定,map的key是數據源,value是Connection的包裝類。
			 * 參數:
			 * 		key		一般是一個數據源
			 * 		value	一般是一個封裝了Connection的包裝類,eg:ConnectionHolder
			 * 
			 * @param key 	the key to bind the value to (usually the resource factory eg:DataSource) 	
			 * @param value the value to bind (usually the active resource object)						value
			 * @throws IllegalStateException if there is already a value bound to the thread
			 * @see ResourceTransactionManager#getResourceFactory()
			 */
			public static void bindResource(Object key, Object value) throws IllegalStateException {
				Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
				Assert.notNull(value, "Value must not be null");
				Map<Object, Object> map = resources.get();
				// set ThreadLocal Map if none found
				if (map == null) {
					map = new HashMap<Object, Object>();
					resources.set(map);
				}
				Object oldValue = map.put(actualKey, value);
				// Transparently suppress a ResourceHolder that was marked as void...
				if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
					oldValue = null;
				}
				if (oldValue != null) {
					throw new IllegalStateException("Already value [" + oldValue + "] for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
				}
				if (logger.isTraceEnabled()) {
					logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" + Thread.currentThread().getName() + "]");
				}
			}
			

			/**
			 * Retrieve a resource for the given key that is bound to the current thread.
			 * 獲取當前線程持有的Connection
			 */
			public static Object getResource(Object key) {
				Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
				Object value = doGetResource(actualKey);
				if (value != null && logger.isTraceEnabled()) {
					logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
							Thread.currentThread().getName() + "]");
				}
				return value;
			}

			private static Object doGetResource(Object actualKey) {
				Map<Object, Object> map = resources.get();
				
				if (map == null) { return null; }
				
				Object value = map.get(actualKey);
				// Transparently remove ResourceHolder that was marked as void...
				if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
					map.remove(actualKey);
					// Remove entire ThreadLocal if empty...
					if (map.isEmpty()) {
						resources.remove();
					}
					value = null;
				}
				return value;
			}
		}

	3>Spring提供的數據源操做工具:DataSourceUtils
	
		package org.springframework.jdbc.datasource;
		
		public abstract class DataSourceUtils {
		
			private static final Log logger = LogFactory.getLog(DataSourceUtils.class);

			/**
			 * Obtain a Connection from the given DataSource. 
			 * 從指定的DataSource獲取一個Connection。
			 * 
			 * 注意:
			 * 	1)不是直接去鏈接池中獲取鏈接,而是去當前線程的一個ThreadLocal變量中獲取。
			 * 	2)MyBatis和Spring整合後,MyBatis在執行sql時:會使用Spring提供的DataSourceUtils.getConnection()方法來得到當前線程綁定的鏈接(Connection),而後經過該鏈接來操做數據庫。
			 * 
			 */
			public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
				try {
					return doGetConnection(dataSource);
				}
				catch (SQLException ex) {
					throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
				}
			}

			public static Connection doGetConnection(DataSource dataSource) throws SQLException {
				Assert.notNull(dataSource, "No DataSource specified");

				ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
				if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
					conHolder.requested();
					if (!conHolder.hasConnection()) {
						logger.debug("Fetching resumed JDBC Connection from DataSource");
						conHolder.setConnection(dataSource.getConnection());
					}
					return conHolder.getConnection();
				}
				// Else we either got no holder or an empty thread-bound holder here.

				logger.debug("Fetching JDBC Connection from DataSource");
				Connection con = dataSource.getConnection();

				if (TransactionSynchronizationManager.isSynchronizationActive()) {
					logger.debug("Registering transaction synchronization for JDBC Connection");
					// Use same Connection for further JDBC actions within the transaction.
					// Thread-bound object will get removed by synchronization at transaction completion.
					ConnectionHolder holderToUse = conHolder;
					if (holderToUse == null) {
						holderToUse = new ConnectionHolder(con);
					} else {
						holderToUse.setConnection(con);
					}
					holderToUse.requested();
					TransactionSynchronizationManager.registerSynchronization(new ConnectionSynchronization(holderToUse, dataSource));
					holderToUse.setSynchronizedWithTransaction(true);
					if (holderToUse != conHolder) {
						TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
					}
				}
				return con;
			}
		}
		
	4>事務定義類:

		package org.springframework.transaction;
		import java.sql.Connection;
		
		public interface TransactionDefinition {

			// 事務的傳播行爲
			int PROPAGATION_REQUIRED = 0;		// 若是當前存在事務,則加入該事務;若是當前沒有事務,則建立一個新的事務。這是默認值。											
			int PROPAGATION_SUPPORTS = 1;		// 若是當前存在事務,則加入該事務;若是當前沒有事務,則以非事務的方式繼續運行。
			int PROPAGATION_MANDATORY = 2;		// 若是當前存在事務,則加入該事務;若是當前沒有事務,則拋出異常。
			int PROPAGATION_REQUIRES_NEW = 3;	// 建立一個新的事務,若是當前存在事務,則把當前事務掛起。
			int PROPAGATION_NOT_SUPPORTED = 4;	// 以非事務方式運行,若是當前存在事務,則把當前事務掛起。
			int PROPAGATION_NEVER = 5;			// 以非事務方式運行,若是當前存在事務,則拋出異常。
			int PROPAGATION_NESTED = 6;			// 若是當前存在事務,則建立一個事務做爲當前事務的嵌套事務來運行;若是當前沒有事務,則等價於PROPAGATION_REQUIRED。

			
			// 事務的隔離級別
			int ISOLATION_DEFAULT = -1;			// 使用底層數據庫的默認隔離級別。
			int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
			int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
			int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
			int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;

			/**
			 * Use the default timeout of the underlying transaction system, or none if timeouts are not supported.
			 */
			// 事務超時:指一個事務所容許執行的最長時間,若是超過該時間限制但事務尚未完成,則自動回滾事務。
			// 默認爲底層事務系統的超時值,若是底層數據庫事務系統沒有設置超時值,那麼就是none,沒有超時限制。
			int TIMEOUT_DEFAULT = -1;

			int getPropagationBehavior();

			int getIsolationLevel();

			int getTimeout();

			boolean isReadOnly();

			String getName();
		}

3)@Transactional註解sql

屬性					類型							描述
value					String				可選的限定描述符,指定使用的事務管理器
propagation				enum 				可選的事務傳播行爲設置
isolation				enum 				可選的事務隔離級別設置
readOnly				boolean				讀寫或只讀事務,默認讀寫
timeout					int 				(in seconds granularity)事務超時時間設置

rollbackFor				(繼承Throwable的)Class數組		致使事務回滾的異常類數組
rollbackForClassName	(繼承Throwable的)類名數組		致使事務回滾的異常類名字數組
noRollbackFor			(繼承Throwable的)Class數組		不會致使事務回滾的異常類數組
noRollbackForClassName	(繼承Throwable的)類名數組		不會致使事務回滾的異常類名字數組

用法:能夠寫在類(接口)上,也能夠方法上。
	1)看成用於類上時,該類的全部public方法將都具備該類型的事務屬性。
	2)能夠在方法上使用該註解來覆蓋類級別上的設置。
	3)Spring建議不要在接口或接口方法上使用該註解,由於只有在使用基於接口的代理時它纔會生效。
	4)在private、protected、default的方法上使用該註解,則該註解將被忽略,也不會拋出任何異常。
	5)默認狀況下,只有來自外部的方法調用纔會被AOP代理捕獲,也就是說:類內部方法調用本類內部的其它方法並不會引發事務行爲,即便被調用方法使用@Transactional註解進行修飾!

4)spring中事務的回滾:數據庫

1)spring推薦以拋出異常的方式來回滾一個事務。
2)spring事務管理器會捕捉任何未處理的異常,而後根據相應的配置來判斷是否回滾拋出異常的事務。
	1>默認配置下,spring只有在拋出的異常爲運行時unchecked異常時纔回滾該事務,也就是拋出的異常爲RuntimeException的子類(Errors也會致使事務回滾),而拋出checked異常則不會致使事務回滾。
	2>能夠配置在拋出哪些異常時回滾事務,包括checked異常;也能夠明確設置哪些異常拋出時不回滾事務。
相關文章
相關標籤/搜索