Transactions in Spring

1) Configuration file:

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) Principle:

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)绑定到当前线程中。
	说明:要想将多个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);
				}
			}
			
			// *********************** TransactionSynchronizationManager源码 start ***********************
			
				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");
							
					/**
					 * Bind the given resource for the given key to the current thread.
					 * 参数:
					 * 		key		通常是一个数据源
					 * 		value	通常是一个封装了Connection的包装类,eg:ConnectionHolder
					 * 
					 * 将当前线程和一个map绑定,map的key是数据源,value是Connection的包装类。
					 * 
					 * @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() + "]");
						}
					}
				}

				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;
					}
				}
				
			// *********************** TransactionSynchronizationManager源码  end  ***********************

			@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>事务定义类:

		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 annotation

属性					类型							描述
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) Rollback of transactions in spring:

1)spring推荐以抛出异常的方式来回滚一个事务。
2)spring事务管理器会捕捉任何未处理的异常,然后根据相应的配置来判断是否回滚抛出异常的事务。
	1>默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。
	2>可以配置在抛出哪些异常时回滚事务,包括checked异常;也可以明确设置哪些异常抛出时不回滚事务。

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325202371&siteId=291194637