分析Spring如何管理Hibernate事务

前言

本文从源码层面分析Spring是如何来管理Hibernate事务的,我认为换作其它的ORM框架也是类似的思想,以此来触类旁通。如果分析有误,欢迎指正。
分析路线是从JDBC管理事务 => Hibernate管理事务 => Spring管理Hibernate事务,逐步递进。

JDBC事务

我们先回忆一下JDBC是如何开启事务的,因为无论再怎么高级的封装,最终的根本还是在于基础。
JDBC中开启事务的过程看起来比较简单,简单的说明以下代码:
#1、加载mysql驱动com.mysql.jdbc.Driver
#2、通过DiverManager获取数据库连接
#3、开启事务,即是关闭自动提交connection.setAutoCommit(false)
#4、开启事务后就可以执行自己的业务,往表中增删改查数据
#5、若#4没发生异常,则提交事务
#6、若#4发生异常,那么捕获到异常,回滚事务
#7、最后释放连接

package com.frank.transaction;

import java.sql.Connection;
import java.sql.DriverManager;

public class JdbcTest {
	
	public static void main(String[] args) throws Exception {
		Class.forName("com.mysql.jdbc.Driver");	//#1
		String url = "jdbc:mysql://127.0.0.1:3306/frank?useUnicode=true&characterEncoding=utf-8&useSSL=false";
		Connection connection = DriverManager.getConnection(url, "root", "123"); //#2
		try {
			connection.setAutoCommit(false);//关闭自动提交(开启事务) #3
		    //... 此处执行多条SQL语句 #4
		    connection.commit();			// 提交事务 #5
		} catch (Exception e) {
			if(connection != null) {
				connection.rollback();		// 回滚事务 #6
			}
		} finally {
			if(connection != null) {
				connection.setAutoCommit(true);
				connection.close();			//关闭连接 #7
			}
		}
	}
	
}

Hibernate事务

Hibernate中操作事务和JDBC的步骤大致相同,只是对JDBC做了一个简单的封装,Hibernate中的Session可以对应JDBC的Connection

package com.frank.transaction;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

public class HibernateTest {
	
	public static void main(String[] args) {
		Configuration configuration = new Configuration().configure("conf/hibernate/hibernate.cfg.xml");
		SessionFactory sessionFacotry = configuration.buildSessionFactory();
		Session session = sessionFacotry.openSession();
		Transaction transaction = null;
		try {
			transaction = session.beginTransaction();	//开启事务,其中就是封装了connection.setAutoCommit(false)
			//... 此处执行数据库操作
			transaction.commit();			//提交事务
		} catch (Exception e) {
			if(transaction != null) {
				transaction.rollback();		//回滚事务
			}
		} finally {
			if(session != null) {
				session.close();			//关闭连接
			}
		}
	}
}

Spring管理Hibernate事务

上面的方式每次都需要使用try{} catch{} finally{}语句块去开启事务、回滚事务释放连接,Spring事务可以简化这些重复的工作,让代码专注于业务更加简洁。
1、想要理清楚这里面的来龙去脉,先从Spring怎么整合Hibernate开始,这也是一个很关键的点。咱们这里使用XML配置来理一理怎么整合的,下面的xml只写出了有关hibernate的配置和开启事务的配置。

<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean" lazy-init="false">
   <!-- 注入数据源 -->
    <property name="dataSource" ref="dataSource" />
    <property name="configLocation" value="classpath:hibernate.cfg.xml"></property>
    <!-- //加载实体类的映射文件位置及名称 -->
    <property name="mappingLocations" value="classpath:com/frank/po/*.hbm.xml"></property>
</bean>  

<!-- 配置Spring声明式事务 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"></property>
</bean> 

<!-- 开启spring事务注解@Transactional -->
<tx:annotation-driven/>

第一个bean配置是使用了Spring提供的LocalSessionFactoryBean工厂bean来帮助我们生成Hibernate的org.hibernate.SessionFactory实例。
第二个bean配置是配置了Spring为Hibernate提供的事务管理器。
以前在做这些配置的时候总是不知道为什么这样配置,在经过下面分析后相信会恍然大悟的。

2、Spring事务处理是通过AOP功能来实现的,AOP的本质又是JDK或者CGLIB动态代理。所以我们为Spring管理的类的某个方法标注@Transactional或者在XML中配置事务,其本质是为此类生成了动态代理,并且织入了通知。

创建代理的过程比较复杂,这里就不分析了,有兴趣的可以从这个类开始分析org.springframework.transaction.interceptor.TransactionProxyFactoryBean,这个类的作用即是为某个类创建代理类并开启事务。XML配置如下:

<bean id="TestServiceTarget"
	class="com.frank.TestServiceImpl">
</bean>

<bean id="TestService"
	class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
	<!-- 配置使用的事务管理器 -->
	<property name="transactionManager" ref="transactionManager" />
	<property name="target" ref="TestServiceTarget" />
	<property name="proxyInterfaces">
		<list>
			<value>com.frank.TestService</value>
		</list>
	</property>
	<property name="transactionAttributes">
		<props>
			<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
			<prop key="update*">PROPAGATION_REQUIRED</prop>
		</props>
	</property>
</bean>

最终给出结论是创建代理后,每个配置了事务的方法被调用时会执行org.springframework.transaction.interceptor.TransactionInterceptor 类的 invoke(MethodInvocation)方法,将目标对象原本的方法拦截。

3、那么接下来分析TransactionInterceptor的invoke方法。

@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
	Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
	return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}

invoke方法主要是在获取代理目标的Class对象,然后调用invokeWithinTransaction方法传入被拦截的方法,目标Class,和回调方法。

4、下面是invokeWithinTransaction方法,我省略了一些与现在分析环境无关的一些代码。

protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
		final InvocationCallback invocation) throws Throwable {
	TransactionAttributeSource tas = getTransactionAttributeSource();
	//TransactionAttribute这里面是设置的一些事务属性如传播行为,隔离界别,回滚规则等。
	final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
	//获取事务管理器,因为我们整合时配置了HibernateTransactionManager,这里就获取到它
	final PlatformTransactionManager tm = determineTransactionManager(txAttr);
	final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

	//这里进入这个分支
	if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
		//创建事务
		TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

		Object retVal;
		try {
			//执行回调函数,拦截器链,没有拦截器了就会执行目标类的方法
			retVal = invocation.proceedWithInvocation();
		}
		catch (Throwable ex) {
			//抛出异常时对事务的处理(回滚)
			completeTransactionAfterThrowing(txInfo, ex);
			throw ex;
		}
		finally {
			//清除绑定事务信息
			cleanupTransactionInfo(txInfo);
		}

		if (vavrPresent && VavrDelegate.isVavrTry(retVal)) {
			// Set rollback-only in case of Vavr failure matching our rollback rules...
			TransactionStatus status = txInfo.getTransactionStatus();
			if (status != null && txAttr != null) {
				retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
			}
		}

		//提交事务
		commitTransactionAfterReturning(txInfo);
		return retVal;
	}

这个方法中是不是感觉类似前面的JDBC和Hibernate开启事务的过程,这是Spring对原始的过程进行了封装的结果。

5、我们继续分析创建事务的过程:createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

@SuppressWarnings("serial")
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
		@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
	if (txAttr != null && txAttr.getName() == null) {
		txAttr = new DelegatingTransactionAttribute(txAttr) {
			@Override
			public String getName() {
				return joinpointIdentification;
			}
		};
	}
	TransactionStatus status = null;
	if (txAttr != null) {
		if (tm != null) {
			//通过事务管理器获得事务状态
			status = tm.getTransaction(txAttr);
		}
		else {
			if (logger.isDebugEnabled()) {
				logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
						"] because no transaction manager has been configured");
			}
		}
	}
	return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}

这个方法中最关键的就是通过事务管理器获取事务(即开启事务)
status = tm.getTransaction(txAttr);

6、继续分析getTransaction,该方法在PlatformTransactionManager的抽象子类中AbstractPlatformTransactionManager

@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
		throws TransactionException {
	TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
	//获得当前线程绑定的事务,只有事物嵌套的时候才会获取到
	Object transaction = doGetTransaction();
	boolean debugEnabled = logger.isDebugEnabled();

	if (isExistingTransaction(transaction)) {
		return handleExistingTransaction(def, transaction, debugEnabled);
	}

	if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
		throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
	}

	if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
		throw new IllegalTransactionStateException(
				"No existing transaction found for transaction marked with propagation 'mandatory'");
	}
	else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
			def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
			def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
		SuspendedResourcesHolder suspendedResources = suspend(null);
		if (debugEnabled) {
			logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
		}
		try {
			boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
			DefaultTransactionStatus status = newTransactionStatus(
					def, transaction, true, newSynchronization, debugEnabled, suspendedResources);
			//** 开启事务 **
			doBegin(transaction, def);
			prepareSynchronization(status, def);
			return status;
		}
		catch (RuntimeException | Error ex) {
			resume(null, suspendedResources);
			throw ex;
		}
	}
	else {
		if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
			logger.warn("Custom isolation level specified but no actual transaction initiated; " +
					"isolation level will effectively be ignored: " + def);
		}
		boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
		return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
	}
}

此方法在抽象类中,Spring考虑了事务嵌套的过程,所以才定义了事务的传播行为。getTransaction方法主要做了两件事:1、获取当前线程绑定的事务,若存在事务,那么根据其传播行为来处理;2、若不存在那么根据当前定义的传播行为来选择是否开启事务。
我们这里为了分析简单,假设事务没有嵌套,并且事务的传播行为也是默认的PROPAGATION_REQUIRED。那么这样就会调用doBegin(transaction, def);来开启事务。

7、doBegin方法需要子类来实现,由于我们配置的是HibernateTransactionManager,所以会调用该类中的方法。

@Override
@SuppressWarnings("deprecation")
protected void doBegin(Object transaction, TransactionDefinition definition) {
	HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;

	if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
		throw new IllegalTransactionStateException(
				"Pre-bound JDBC Connection found! HibernateTransactionManager does not support " +
				"running within DataSourceTransactionManager if told to manage the DataSource itself. " +
				"It is recommended to use a single HibernateTransactionManager for all transactions " +
				"on a single DataSource, no matter whether Hibernate or JDBC access.");
	}

	Session session = null;

	try {
		if (!txObject.hasSessionHolder() || txObject.getSessionHolder().isSynchronizedWithTransaction()) {
			Interceptor entityInterceptor = getEntityInterceptor();
			//*这里是重点,这里会获取注入的SessionFactory调用其openSession方法来打开一个session回话
			Session newSession = (entityInterceptor != null ?
					obtainSessionFactory().withOptions().interceptor(entityInterceptor).openSession() :
					obtainSessionFactory().openSession());
			if (logger.isDebugEnabled()) {
				logger.debug("Opened new Session [" + newSession + "] for Hibernate transaction");
			}
			txObject.setSession(newSession);
		}

		session = txObject.getSessionHolder().getSession();

		boolean holdabilityNeeded = this.allowResultAccessAfterCompletion && !txObject.isNewSession();
		boolean isolationLevelNeeded = (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT);
		if (holdabilityNeeded || isolationLevelNeeded || definition.isReadOnly()) {
			if (this.prepareConnection && isSameConnectionForEntireSession(session)) {
				if (logger.isDebugEnabled()) {
					logger.debug("Preparing JDBC Connection of Hibernate Session [" + session + "]");
				}
				Connection con = ((SessionImplementor) session).connection();
				Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
				txObject.setPreviousIsolationLevel(previousIsolationLevel);
				if (this.allowResultAccessAfterCompletion && !txObject.isNewSession()) {
					int currentHoldability = con.getHoldability();
					if (currentHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT) {
						txObject.setPreviousHoldability(currentHoldability);
						con.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
					}
				}
			}
			else {
				if (isolationLevelNeeded) {
					throw new InvalidIsolationLevelException(
							"HibernateTransactionManager is not allowed to support custom isolation levels: " +
							"make sure that its 'prepareConnection' flag is on (the default) and that the " +
							"Hibernate connection release mode is set to 'on_close' (the default for JDBC).");
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Not preparing JDBC Connection of Hibernate Session [" + session + "]");
				}
			}
		}

		if (definition.isReadOnly() && txObject.isNewSession()) {
			session.setFlushMode(FlushMode.MANUAL);
			session.setDefaultReadOnly(true);
		}

		if (!definition.isReadOnly() && !txObject.isNewSession()) {
			FlushMode flushMode = SessionFactoryUtils.getFlushMode(session);
			if (FlushMode.MANUAL.equals(flushMode)) {
				session.setFlushMode(FlushMode.AUTO);
				txObject.getSessionHolder().setPreviousFlushMode(flushMode);
			}
		}

		Transaction hibTx;

		//下面这段即是在调用hibernate的seesion开启事物
		int timeout = determineTimeout(definition);
		if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
			//如果有定义超时时间,那么通过session获取事务设置超时时间,然后开启事务
			hibTx = session.getTransaction();
			hibTx.setTimeout(timeout);
			hibTx.begin();
		}
		else {
			//直接开启事务
			hibTx = session.beginTransaction();
		}
		
		//将事务设置到HibernateTransactionObject的SessionHolder中去
		txObject.getSessionHolder().setTransaction(hibTx);

		if (getDataSource() != null) {
			SessionImplementor sessionImpl = (SessionImplementor) session;
			ConnectionHolder conHolder = new ConnectionHolder(() -> sessionImpl.connection());
			if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
				conHolder.setTimeoutInSeconds(timeout);
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Exposing Hibernate transaction as JDBC [" + conHolder.getConnectionHandle() + "]");
			}
			TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
			txObject.setConnectionHolder(conHolder);
		}

		//如果事务是才开启的,那么将其绑定
		if (txObject.isNewSessionHolder()) {
			//将SessionHolder绑定到当前线程,以sessionFactory为key
			//TransactionSynchronizationManager内部其实就是用的ThreadLocal来保存
			TransactionSynchronizationManager.bindResource(obtainSessionFactory(), txObject.getSessionHolder());
		}
		txObject.getSessionHolder().setSynchronizedWithTransaction(true);
	}

	catch (Throwable ex) {
		if (txObject.isNewSession()) {
			try {
				if (session != null && session.getTransaction().getStatus() == TransactionStatus.ACTIVE) {
					session.getTransaction().rollback();
				}
			}
			catch (Throwable ex2) {
				logger.debug("Could not rollback Session after failed transaction begin", ex);
			}
			finally {
				SessionFactoryUtils.closeSession(session);
				txObject.setSessionHolder(null);
			}
		}
		throw new CannotCreateTransactionException("Could not open Hibernate Session for transaction", ex);
	}
}

该方法做的事情就是拿到我们在xml中注入的SessionFactory打开一个session,然后根据事务设置的属性如超时、隔离级别等对session进行设置,最后会通过TransactionSynchronizationManager#bindResource方法将该session绑定到当前线程中。

8、分析到此,原先Hibernate开启事务简单的两句话,变得如此复杂。
Session session = sessionFacotry.openSession();
Transaction transaction = session.beginTransaction();
现在让我们回顾一下,在第4步分析中的invokeWithinTransaction方法中在创建事务后会得到TransactionInfo事务信息,这里面会保存着创建的session,那么后面就可以使用保存的session来提交事务或者回滚事务了。

现在有个问题,在我们的业务代码中肯定要使用绑定到当前线程中的session来进行数据库操作才会具有有事务的特性,那么肯定是要通过TransactionSynchronizationManager来取得当前线程绑定的session的,于是我分析了HibernateTemplatesaveOrUpdate方法,发现其最终是这样获取的session,这里根本没有涉及到TransactionSynchronizationManager

try {
	session = obtainSessionFactory().getCurrentSession();
}
catch (HibernateException ex) {
	logger.debug("Could not retrieve pre-bound Hibernate session", ex);
}
if (session == null) {
	session = obtainSessionFactory().openSession();
	session.setFlushMode(FlushMode.MANUAL);
	isNew = true;
}

这其中就要涉及到Hibernate的openSession和getCurrentSession的区别,getCurrentSession获取session会去上下文中寻找,Hibernate又可以让我们自己实现这个上下文只需要实现org.hibernate.context.spi.CurrentSessionContext接口,在创建SessionFactory时通过参数hibernate.current_session_context_class来指定自定义的实现类。

Spring就是这样搞的,还记得我们整合Hibernate的时候吗,我们通过配置Spring提供的org.springframework.orm.hibernate4.LocalSessionFactoryBean工厂bean来生产的SessionFactory实例。就在这个LocalSessionFactoryBean中有玄机,该类实现了InitializingBean接口,所以实例化时会调用afterPropertiesSet()方法,其中通过LocalSessionFactoryBuilder来配置Hibernate,在其构造方法中就有一句代码

//AvailableSettings.CURRENT_SESSION_CONTEXT_CLASS = "hibernate.current_session_context_class"
getProperties().put(AvailableSettings.CURRENT_SESSION_CONTEXT_CLASS, SpringSessionContext.class.getName());

所以再调用SessionFactory#getCurrentSession()方法时,会调用SpringSessionContext的currentSession()来获取session,在该方法中就会通过TransactionSynchronizationManager去获得绑定到当前线程的SessionHolder。

总结

Spring管理事务整体步骤:
1、为目标类生成代理类,将业务代码包裹在环绕通知中
2、使用配置的事务管理器开启事务,然后通过TransactionSynchronizationManager.bindResource(Object, Object)将此连接绑定到当前线程
3、业务代码也必须使用使用相同的连接才会具有事务特性,所以也需通过TransactionSynchronizationManager.getResource(Object)方法来获取连接执行数据库操作。
4、若业务抛出需要回滚的异常,那么事务管理器回滚事务,若正常运行则提交事务。

至此我认为应该能明白为什么整合Hibernate需要那么配置了,因为这两个类需要互相搭配才能使用,中间的桥梁则是TransactionSynchronizationManager

下面是Spring事务管理器的类层次结构设计图:
Spring事务管理器的类层次结构

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值