本人对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<SessionFactory>…….
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) {// JpaTransactionManagerreturn ((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事务管理器为
HibernateTransactionManagerHibernateTransactionManager 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() || txObject.getSessionHolder().isSynchronizedWithTransaction() 返回ture则会新建session
其中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>> resourcesSessionHolder 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进入源码, 暂时未追踪运行时进入事务源码的代码,且留个疑问, 有暇在外网搭建个环境的时候,再做补充吧.