转载

因事务导致hibernate延迟加载出现no session异常

文章来源: 临窗旋墨的博客

本人对hibernate 的使用不是特别的熟悉,这里只是记录一次帮助同事解决异常排查的过程.

项目中的spring版本为4.1.6

贴出的源码的spring版本为5.1.9

1 项目技术框架

spring + springmvc + hibernate + freemarker

2 异常的产生现象

controller中直接调用serviceA中的方法A 页面可正常渲染;

controller调用serviceB中的方法B, 方法B中调用serviceA中的方法A,渲染页面的时候报异常:no session

3 问题追踪

  1. 根据现象,最直观的原因是session已经关闭, 然后获取懒加载对象的属性的时候,没有获取到session;
  2. 直接调用无错, 但是经过另外一个service调用后就no session, 说明两个session可能并不是一个session

3.1 确认是否是一个session的调试:结果不是一个session;

本项目开启了 OpenSessionInViewFilter 过滤器,

  1. 断点跟踪OpenSessionInViewFilter中的open的Session, 记录sesson的hashcode-->code1;
  2. 进入serviceB, 通过获取 sessionFactory.getCurrentSession() , 记录session的hashcode, 等于code1, 表示此处使用的session是filter中打开的session;
  3. 从serviceB进入serviceA,获取当前session,获得的hashcode值与 code1 不一致 ;

3.2 为什么不是一个session: 因为不在一个事物中

  1. 本项目的事务在配置文件中由切面配置;
  2. 由于命名方式的问题,
    1. 入口方法B不要求在事物中运行( propagation="SUPPORTS")
    2. 方法A需要在一个事物中运行((propagation="REQUIRED"))
    3. 导致进入方法B后不需要事务,但是B调用的A方法在事务中运行

关于事务的传播性,小伙伴可自行复习;

3.3 解决问题:修改方法名,保证在一个事务中

修改方法名后, 保证两个方法在一个事务中,再次验证, 两者session一致, 延迟加载的属性可正常加载, 在OpenSessionInViewFilter的finally代码块中亦可正常关闭;

4 再问:openSession和getCurrentSession的区别

  • 采用getCurrentSession()创建的session在commit或rollback时会自动关闭,
  • 采用openSession()创建的session必须手动关闭
  • 采用getCurrentSession()创建的session会绑定到当前线程中,
  • 采用openSession() 创建的session则不会.所以以上的session是一直都是一个session.

本问题中因为方法A中 session (getCurrentSession())在事务中运行, 所以在commit之后被关闭掉了,造成延迟加载事务失败;

5 三问: 为什么开启新事物中的session和当前线程中的session不一致

5.1 spring如何管理hibernate的session

查看配置文件中关于sessionFactory的配置为 LocalSessionFactoryBean

LocalSessionFactoryBean` implements FactoryBean.......

SessionFactory.getCurrentSession源码追踪

SessionFactoryImpl#getCurrentSession

public Session getCurrentSession() throws HibernateException {
    if ( currentSessionContext == null ) {
        throw new HibernateException( "No CurrentSessionContext configured!" );
    }
    return currentSessionContext.currentSession();
}
复制代码

SpringSessionContext#currentSession

public Session currentSession() throws HibernateException {
        /*
        *以sessionFactory为key去当前线程中获取session(此处不再展开源码), 可能是在 OpenSessionInViewFilter 中存入线程的, 参见 OpenSessionInViewFilter 的openSessIon代码
        */
		Object value = TransactionSynchronizationManager.getResource(this.sessionFactory);
		if (value instanceof Session) {
			return (Session) value;
		}
		else if (value instanceof SessionHolder) {
			// hibernate事务管理器
			SessionHolder sessionHolder = (SessionHolder) value;
			Session session = sessionHolder.getSession();
			if (!sessionHolder.isSynchronizedWithTransaction() &&
					TransactionSynchronizationManager.isSynchronizationActive()) {
				TransactionSynchronizationManager.registerSynchronization(
						new SpringSessionSynchronization(sessionHolder, this.sessionFactory, false));
				sessionHolder.setSynchronizedWithTransaction(true);
				// Switch to FlushMode.AUTO, as we have to assume a thread-bound Session
				// with FlushMode.MANUAL, which needs to allow flushing within the transaction.
				FlushMode flushMode = SessionFactoryUtils.getFlushMode(session);
				if (flushMode.equals(FlushMode.MANUAL) &&
						!TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
					session.setFlushMode(FlushMode.AUTO);
					sessionHolder.setPreviousFlushMode(flushMode);
				}
			}
			return session;
		}
		else if (value instanceof EntityManagerHolder) {
			// JpaTransactionManager
			return ((EntityManagerHolder) value).getEntityManager().unwrap(Session.class);
		}

		if (this.transactionManager != null && this.jtaSessionContext != null) {
			try {
				if (this.transactionManager.getStatus() == Status.STATUS_ACTIVE) {
					Session session = this.jtaSessionContext.currentSession();
					if (TransactionSynchronizationManager.isSynchronizationActive()) {
						TransactionSynchronizationManager.registerSynchronization(
								new SpringFlushSynchronization(session));
					}
					return session;
				}
			}
			catch (SystemException ex) {
				throw new HibernateException("JTA TransactionManager found but status check failed", ex);
			}
		}

		if (TransactionSynchronizationManager.isSynchronizationActive()) {
			Session session = this.sessionFactory.openSession();
			if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
				session.setFlushMode(FlushMode.MANUAL);
			}
			SessionHolder sessionHolder = new SessionHolder(session);
			TransactionSynchronizationManager.registerSynchronization(
					new SpringSessionSynchronization(sessionHolder, this.sessionFactory, true));
			TransactionSynchronizationManager.bindResource(this.sessionFactory, sessionHolder);
			sessionHolder.setSynchronizedWithTransaction(true);
			return session;
		}
		else {
			throw new HibernateException("Could not obtain transaction-synchronized Session for current thread");
		}
	}
复制代码

对比OpenSessionInViewFilter#doFilterInternal ,可以发现两者获取hibernate 的session的逻辑相似;

HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		SessionFactory sessionFactory = lookupSessionFactory(request);
		boolean participate = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		String key = getAlreadyFilteredAttributeName();

		if (TransactionSynchronizationManager.hasResource(sessionFactory)) {
			// Do not modify the Session: just set the participate flag.
			participate = true;
		}
		else {
			boolean isFirstRequest = !isAsyncDispatch(request);
			if (isFirstRequest || !applySessionBindingInterceptor(asyncManager, key)) {
				logger.debug("Opening Hibernate Session in OpenSessionInViewFilter");
				Session session = openSession(sessionFactory);
				SessionHolder sessionHolder = new SessionHolder(session);
				TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);

				AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(sessionFactory, sessionHolder);
				asyncManager.registerCallableInterceptor(key, interceptor);
				asyncManager.registerDeferredResultInterceptor(key, interceptor);
			}
		}

		try {
			filterChain.doFilter(request, response);
		}

		finally {
			if (!participate) {
				SessionHolder sessionHolder =
						(SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
				if (!isAsyncStarted(request)) {
					logger.debug("Closing Hibernate Session in OpenSessionInViewFilter");
					SessionFactoryUtils.closeSession(sessionHolder.getSession());
				}
			}
		}
复制代码

spring是如何管理hibernate的事务的? HibernateTransactionManager

本项目配置的hibernate事务管理器为 HibernateTransactionManager

HibernateTransactionManager extends AbstractPlatformTransactionManager......

其中获取事务的源码参见:AbstractPlatformTransactionManager#getTransaction

public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
		Object transaction = doGetTransaction();

		// Cache debug flag to avoid repeated checks.
		boolean debugEnabled = logger.isDebugEnabled();

		if (definition == null) {
			// Use defaults if no transaction definition given.
			definition = new DefaultTransactionDefinition();
		}

		if (isExistingTransaction(transaction)) {
			// Existing transaction found -> check propagation behavior to find out how to behave.
			return handleExistingTransaction(definition, transaction, debugEnabled);
		}

		// Check definition settings for new transaction.
		if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
			throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
		}

		// No existing transaction found -> check propagation behavior to find out how to proceed.
		if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
			throw new IllegalTransactionStateException(
					"No existing transaction found for transaction marked with propagation 'mandatory'");
		}
		else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
				definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
				definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
			SuspendedResourcesHolder suspendedResources = suspend(null);
			if (debugEnabled) {
				logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
			}
			try {
				boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
				DefaultTransactionStatus status = newTransactionStatus(
						definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
				doBegin(transaction, definition);
				prepareSynchronization(status, definition);
				return status;
			}
			catch (RuntimeException | Error ex) {
				resume(null, suspendedResources);
				throw ex;
			}
		}
		else {
			// Create "empty" transaction: no actual transaction, but potentially synchronization.
			if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
				logger.warn("Custom isolation level specified but no actual transaction initiated; " +
						"isolation level will effectively be ignored: " + definition);
			}
			boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
			return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
		}
	}
复制代码

其中dobegin进入HibernateTransactionManager#doBegin

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 {
            /*** 判断是否 open一个newSession 见下文说明
            txObject.hasSessionHolder() 一般应返回true,因为在filter中新建了
            txObject.getSessionHolder().isSynchronizedWithTransaction() 何时为true呢?
            在try代码块的末尾会设置其为true: txObject.getSessionHolder().setSynchronizedWithTransaction(true);
            有理由相信 可能是第1+n次进入此dobegin方法时候,如果是txObject中的SessionHolder属性为同一个,则这个属性为true.
            那么什么时候第1+n次进入dobegin,却携带了相同的txObject呢?
            参见:AbstractPlatformTransactionManager#
            DefaultTransactionStatus status = newTransactionStatus(
						definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
				doBegin(transaction, definition);
            
            *************************************/
			if (!txObject.hasSessionHolder() || txObject.getSessionHolder().isSynchronizedWithTransaction()) {
				Interceptor entityInterceptor = getEntityInterceptor();
				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)) {
					// We're allowed to change the transaction settings of the JDBC Connection.
					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 {
					// Not allowed to change the transaction settings of the JDBC Connection.
					if (isolationLevelNeeded) {
						// We should set a specific isolation level but are not allowed to...
						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()) {
				// Just set to MANUAL in case of a new Session for this transaction.
				session.setFlushMode(FlushMode.MANUAL);
				// As of 5.1, we're also setting Hibernate's read-only entity mode by default.
				session.setDefaultReadOnly(true);
			}

			if (!definition.isReadOnly() && !txObject.isNewSession()) {
				// We need AUTO or COMMIT for a non-read-only transaction.
				FlushMode flushMode = SessionFactoryUtils.getFlushMode(session);
				if (FlushMode.MANUAL.equals(flushMode)) {
					session.setFlushMode(FlushMode.AUTO);
					txObject.getSessionHolder().setPreviousFlushMode(flushMode);
				}
			}

			Transaction hibTx;

			// Register transaction timeout.
			int timeout = determineTimeout(definition);
			if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
				// Use Hibernate's own transaction timeout mechanism on Hibernate 3.1+
				// Applies to all statements, also to inserts, updates and deletes!
				hibTx = session.getTransaction();
				hibTx.setTimeout(timeout);
				hibTx.begin();
			}
			else {
				// Open a plain Hibernate transaction without specified timeout.
				hibTx = session.beginTransaction();
			}

			// Add the Hibernate transaction to the session holder.
			txObject.getSessionHolder().setTransaction(hibTx);

			// Register the Hibernate Session's JDBC Connection for the DataSource, if set.
			if (getDataSource() != null) {
				SessionImplementor sessionImpl = (SessionImplementor) session;
				// The following needs to use a lambda expression instead of a method reference
				// for compatibility with Hibernate ORM <5.2 where connection() is defined on
				// SessionImplementor itself instead of on SharedSessionContractImplementor...
				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);
			}

			// Bind the session holder to the thread.
			if (txObject.isNewSessionHolder()) {
				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);
		}
	}
复制代码

如上 :若 txObject.hasSessionHolder() || txObject.getSessionHolder().isSynchronizedWithTransaction() 返回ture则会新建session

  1. 其中txObject.hasSessionHolder() 一般应返回true,因为多在OpenSessionInViewFilter中新建了

  2. 那txObject.getSessionHolder().isSynchronizedWithTransaction() 何时为true呢?

  • 在try代码块的末尾会设置其为true: txObject.getSessionHolder().setSynchronizedWithTransaction(true);

  • txObject(HibernateTransactionObject)对象来自doGetTransaction()方法

    • txObject的sessionFactory属性来自:(SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    • 可知在一个线程中确实是一个对象

HibernateTransactionManager#doGetTransaction

protected Object doGetTransaction() {
		HibernateTransactionObject txObject = new HibernateTransactionObject();
		txObject.setSavepointAllowed(isNestedTransactionAllowed());
		//获取 sessionFactory, 来自当前HibernateTransactionManager实例的sessionFactory属性
		SessionFactory sessionFactory = obtainSessionFactory();
        //获取当前线程绑定的sessionHolder,来自TransactionSynchronizationManager 的ThreadLocal<Map<Object, Object>> resources
		SessionHolder sessionHolder, =
				(SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
		if (sessionHolder != null) {
			if (logger.isDebugEnabled()) {
				logger.debug("Found thread-bound Session [" + sessionHolder.getSession() + "] for Hibernate transaction");
			}
			txObject.setSessionHolder(sessionHolder);
		}
		else if (this.hibernateManagedSession) {
			try {
				Session session = sessionFactory.getCurrentSession();
				if (logger.isDebugEnabled()) {
					logger.debug("Found Hibernate-managed Session [" + session + "] for Spring-managed transaction");
				}
				txObject.setExistingSession(session);
			}
			catch (HibernateException ex) {
				throw new DataAccessResourceFailureException(
						"Could not obtain Hibernate-managed Session for Spring-managed transaction", ex);
			}
		}

		if (getDataSource() != null) {
			ConnectionHolder conHolder = (ConnectionHolder)
					TransactionSynchronizationManager.getResource(getDataSource());
			txObject.setConnectionHolder(conHolder);
		}

		return txObject;
	}
复制代码

追踪代码而知, 在新建事务的时候,若当前线程之前已经新建了事务,且进入了doBegin方法,则hibernate的事务对象HibernateTransactionObject中持有的SessionHolder对象的synchronizedWithTransaction属性会被设置为true;在这种情况下,新事务下的session为新打开的session,造成和先前的session不一致的情况.

6 留下的疑问:

项目中的serviceA的A方法并没有开启事务(虽然进入事务切面, 但是传播级别是SUPPORTS)

根据源码只会新建一个空的事务(Create "empty" transaction: no actual transaction, but potentially synchronization.),

而不会进入doBegin,也就不会设置SessionHolder对象的synchronizedWithTransaction属性为true,

那么serviceB的B方法开启新事务的时候, 理应不会打开新的session才对......................

由于项目在内网环境,无法debug进入源码, 暂时未追踪运行时进入事务源码的代码,且留个疑问, 有暇在外网搭建个环境的时候,再做补充吧.

或者有哪些大侠直接告诉我答案也是好的.

文章来源: 临窗旋墨的博客

原文  https://juejin.im/post/5efeebbb5188252e6b288bab
正文到此结束
Loading...