文章来源: 临窗旋墨的博客
本人对hibernate 的使用不是特别的熟悉,这里只是记录一次帮助同事解决异常排查的过程.
项目中的spring版本为4.1.6
贴出的源码的spring版本为5.1.9
spring + springmvc + hibernate + freemarker
controller中直接调用serviceA中的方法A 页面可正常渲染;
controller调用serviceB中的方法B, 方法B中调用serviceA中的方法A,渲染页面的时候报异常:no session
本项目开启了 OpenSessionInViewFilter
过滤器,
sessionFactory.getCurrentSession()
, 记录session的hashcode, 等于code1, 表示此处使用的session是filter中打开的session; code1
不一致
; 关于事务的传播性,小伙伴可自行复习;
修改方法名后, 保证两个方法在一个事务中,再次验证, 两者session一致, 延迟加载的属性可正常加载, 在OpenSessionInViewFilter的finally代码块中亦可正常关闭;
本问题中因为方法A中 session
(getCurrentSession())在事务中运行, 所以在commit之后被关闭掉了,造成延迟加载事务失败;
查看配置文件中关于sessionFactory的配置为 LocalSessionFactoryBean
LocalSessionFactoryBean` implements FactoryBean.......
public Session getCurrentSession() throws HibernateException { if ( currentSessionContext == null ) { throw new HibernateException( "No CurrentSessionContext configured!" ); } return currentSessionContext.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"); } } 复制代码
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()); } } } 复制代码
HibernateTransactionManager
本项目配置的hibernate事务管理器为 HibernateTransactionManager
HibernateTransactionManager extends AbstractPlatformTransactionManager......
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); } } 复制代码
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() 一般应返回true,因为多在OpenSessionInViewFilter中新建了
那txObject.getSessionHolder().isSynchronizedWithTransaction() 何时为true呢?
在try代码块的末尾会设置其为true: txObject.getSessionHolder().setSynchronizedWithTransaction(true);
txObject(HibernateTransactionObject)对象来自doGetTransaction()方法
txObject的sessionFactory属性来自:(SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
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不一致的情况.
项目中的serviceA的A方法并没有开启事务(虽然进入事务切面, 但是传播级别是SUPPORTS)
根据源码只会新建一个空的事务(Create "empty" transaction: no actual transaction, but potentially synchronization.),
而不会进入doBegin,也就不会设置SessionHolder对象的synchronizedWithTransaction属性为true,
那么serviceB的B方法开启新事务的时候, 理应不会打开新的session才对......................
由于项目在内网环境,无法debug进入源码, 暂时未追踪运行时进入事务源码的代码,且留个疑问, 有暇在外网搭建个环境的时候,再做补充吧.
或者有哪些大侠直接告诉我答案也是好的.
文章来源: 临窗旋墨的博客