分析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的,于是我分析了HibernateTemplate
的saveOrUpdate
方法,发现其最终是这样获取的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事务管理器的类层次结构设计图: