由Could not obtain transaction-synchronized Session for current thread错误处理引申到声明式与编程式事务处理

##由***Could not obtain transaction-synchronized Session for current thread***错误处理引申到***声明式与编程式事务处理***

承接上文 ##[***Spring4.1+Hibernate4.3***整合***EhCache***二级缓存设计报***org.hibernate.cache.spi.RegionFactory***或***org.hibernate.engine.spi.CacheImplementor***的处理及细节问题说明](http://blog.csdn.net/MUXINGYE/article/details/54562264" 文章标题") 后续处理

整合版本 spring-framework-4.1.0.RELEASE-dist hibernate-release-4.3.6.Final

网上的各种答案,好搓啊,驴唇不对马嘴。 感觉真是太奇葩。

都不是傻子,说那些配置问题有个屁用。

我看很多是用什么注解解决的,这根本就不是问题的关键。 感觉纯粹是无稽之谈。

所以。最靠谱的还是看源码的执行喽。

问题的关键涉及到***程序执行的顺序问题以及几个关键方法的调用***

如果当前程序首先执行到了声明式事务的方法

那么***在声明式事务执行的过程中,有很重要的一个对数据源及session的赋值动作***。

源码分析如下

1、说在前面

至于下述源码 为什么这么走,跟你Spring的声明式事务处理的配置相关联, 此处少点废话, 直接贴上我的配置信息。

这是Spring的配置文件

	<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
	    <property name="locations">
	        <value>classpath:dbcp.properties</value>
	    </property>
	</bean>
	
	<bean id="dataSource" destroy-method="close"
	      class="org.apache.commons.dbcp2.BasicDataSource">
	    <property name="driverClassName" value="${jdbc.dbcp.driverClassName}"/>
		……
	</bean>
	
	<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
	    <property name="dataSource" ref="dataSource"/>
	    <property name="configLocations">
	    	<value>classpath:hibernate.cfg.xml</value>
	    </property>
	</bean>
	
	<bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
		<property name="dataSource" ref="dataSource"/>
		<property name="sessionFactory" ref="sessionFactory"/>
	</bean>
	
	<tx:advice id="txServiceAdvice" transaction-manager="txManager">
		<tx:attributes>
			<tx:method name="get*" read-only="true" rollback-for="java.lang.Throwable" />
			<tx:method name="*" rollback-for="java.lang.Throwable" />
		</tx:attributes>
	</tx:advice>
	
	<tx:advice id="txDaoAdvice" transaction-manager="txManager">
		<tx:attributes>
			<tx:method name="get*" read-only="true" rollback-for="java.lang.Throwable" />
			<tx:method name="*" rollback-for="java.lang.Throwable" />
		</tx:attributes>
	</tx:advice>
	
	<aop:config>
		<aop:pointcut 
			expression="execution(* ..service.impl..*.*(..) )" 
			id="declareTransactionServicePC"/>
		<aop:pointcut 
			expression="execution(* fjw..dao.impl..*.*(..) )" 
			id="declareTransactionDaoPC"/>
		
		<aop:advisor advice-ref="txServiceAdvice" pointcut-ref="declareTransactionServicePC" order="10" />
		<aop:advisor advice-ref="txDaoAdvice" pointcut-ref="declareTransactionDaoPC" order="11" />
	</aop:config>

Hibernate的配置文件只需注意两点即可

	<property name="current_session_context_class">
		org.springframework.orm.hibernate4.SpringSessionContext
	</property>
	<property name="hibernate.cache.region.factory_class">
		org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory
	</property>

2、下面请看是细流程

org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(Method, Class<?>, InvocationCallback)
的
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

执行到 -

org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(PlatformTransactionManager, TransactionAttribute, String)
内部的
status = tm.getTransaction(txAttr);

再执行到 -

org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(TransactionDefinition)
内部的 - Object transaction = doGetTransaction();

下述是***关键代码***。 贴的详细一点 ——

	执行到
	org.springframework.orm.hibernate4.HibernateTransactionManager.doGetTransaction()
	方法内的 - 

		SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(getSessionFactory());
		的内部执行流程

			org.springframework.transaction.support.TransactionSynchronizationManager.TransactionSynchronizationManager()

				public abstract class TransactionSynchronizationManager {

					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;
					}
					
			
			Object value = doGetResource(actualKey); 这句是关键所在
			
			第一次执行到此处时,为空,继续向下的话,

		org.springframework.orm.hibernate4.HibernateTransactionManager.doGetTransaction()
		方法内的 - 
				if (getDataSource() != null) {
					ConnectionHolder conHolder = (ConnectionHolder)
							TransactionSynchronizationManager.getResource(getDataSource());
					txObject.setConnectionHolder(conHolder);
				}
				
				会尝试根据数据源完成获取,但是依然为空。


继续执行到方法内部的 - doBegin(transaction, definition);

	内部流程是
	org.springframework.orm.hibernate4.HibernateTransactionManager.doBegin(Object, TransactionDefinition)
	
		HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
		事务在上一步已经获取到了
			是org.springframework.orm.hibernate4.HibernateTransactionManager$HibernateTransactionObject@593444ea
		
		
		继续向下 - 
		
		Session session = null;

		try {
			if (txObject.getSessionHolder() == null || txObject.getSessionHolder().isSynchronizedWithTransaction()) {
				Interceptor entityInterceptor = getEntityInterceptor();
				Session newSession = (entityInterceptor != null ?
						getSessionFactory().withOptions().interceptor(entityInterceptor).openSession() :
						getSessionFactory().openSession());
						
						此步会完成对Session的赋值操作
				if (logger.isDebugEnabled()) {
					logger.debug("Opened new Session [" + newSession + "] for Hibernate transaction");
				}
				txObject.setSession(newSession);
					会将创建的 Session 和 HibernateTransactionManager完成绑定
				
			}

			session = txObject.getSessionHolder().getSession();
				这个时候,才能通过这个方法获取到事务绑定的session
				SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=org.hibernate.engine.spi.ExecutableList@5642ab5d updates=org.hibernate.engine.spi.ExecutableList@330092e9 deletions=org.hibernate.engine.spi.ExecutableList@6aad1927 orphanRemovals=org.hibernate.engine.spi.ExecutableList@a4e8628 collectionCreations=org.hibernate.engine.spi.ExecutableList@2949b161 collectionRemovals=org.hibernate.engine.spi.ExecutableList@251c6c74 collectionUpdates=org.hibernate.engine.spi.ExecutableList@6d1446b4 collectionQueuedOps=org.hibernate.engine.spi.ExecutableList@4e2e0b5e unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])
				
			后续操作是对当前事务的相关参数的设置,就忽略了
			
			内部有一个操作 - 
			// Bind the session holder to the thread.
			if (txObject.isNewSessionHolder()) {
				TransactionSynchronizationManager.bindResource(getSessionFactory(), txObject.getSessionHolder());
			}
			完成了绑定操作
			

问题的关键来了

后期在使用到 - org.hibernate.internal.SessionFactoryImpl.getCurrentSession() 的时候
	从org.hibernate.internal.SessionFactoryImpl.getCurrentSession()
		内部的currentSessionContext.currentSession();
	到org.springframework.orm.hibernate4.SpringSessionContext.currentSession()
		内部的Object value = TransactionSynchronizationManager.getResource(this.sessionFactory);
			内部会执行到上述分析的
			org.springframework.transaction.support.TransactionSynchronizationManager.getResource(Object)
				这个时候,是有值的。因为上述完成了赋值操作
				org.springframework.orm.hibernate4.SessionHolder@68a908d
				
		那么后续的Session session = sessionHolder.getSession();
		就可以正常获取,后续的操作也是没有问题的。

但是,如果你没有正常的声明式事务处理的事务的相关操作

会在
org.springframework.orm.hibernate4.SpringSessionContext.currentSession()
方法内部抛出
throw new HibernateException("Could not obtain transaction-synchronized Session for current thread");

3、问题的解决方案可以有四 ——

一是取消非声明式事务方法绑定的相关数据库操作。杀鸡取卵。PASS。

二是在声明式事务的方法调用之前的相关事务处理的方法调用使用 原生的HibernateAPI,不要考虑声明式事务问题。

但是这种写法在后期会报出***事务嵌套问题***,不推荐。

三是,使用 原生的HibernateAPI的时候使用openSession,手动处理事务回滚问题。 - 推荐

形如下述代码模式

		Transaction transaction = null;
		Session session = null;
		try {
			session = HibernateUtil.openSession();
			transaction = session.beginTransaction();
			……
			transaction.commit();
		} catch (Exception e) {
			if (transaction!=null) transaction.rollback();
		} finally {
			if (session!=null) session.close();
		}

四是,将所有相关的方法采用声明式事务处理,防止getCurrentSession()获取不到 - 麻烦,没必要。不考虑。

最终结论就是, 大量重复代码采用声明式事务处理即可。 偶尔的操作,使用编程式处理,并且在声明式事务方法之前执行的代码。只能openSession。而不能getCurrentSession。

如需要转载 请注明文章出处 https://my.oschina.net/u/3032872/blog/1648371 谢谢~

猜你喜欢

转载自my.oschina.net/u/3032872/blog/1648371