因事务导致hibernate延迟加载出现no session异常的问题排查

文章来源原创   作者:许秋冬   发布时间:2020-07-03   阅读:1520   标签:源码,事务

因事务导致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>…….

SessionFactory.getCurrentSession源码追踪
SessionFactoryImpl#getCurrentSession
  1. public Session getCurrentSession() throws HibernateException {
  2. if ( currentSessionContext == null ) {
  3. throw new HibernateException( "No CurrentSessionContext configured!" );
  4. }
  5. return currentSessionContext.currentSession();
  6. }
SpringSessionContext#currentSession
  1. public Session currentSession() throws HibernateException {
  2. /*
  3. *以sessionFactory为key去当前线程中获取session(此处不再展开源码), 可能是在 OpenSessionInViewFilter 中存入线程的, 参见 OpenSessionInViewFilter 的openSessIon代码
  4. */
  5. Object value = TransactionSynchronizationManager.getResource(this.sessionFactory);
  6. if (value instanceof Session) {
  7. return (Session) value;
  8. }
  9. else if (value instanceof SessionHolder) {
  10. // hibernate事务管理器
  11. SessionHolder sessionHolder = (SessionHolder) value;
  12. Session session = sessionHolder.getSession();
  13. if (!sessionHolder.isSynchronizedWithTransaction() &&
  14. TransactionSynchronizationManager.isSynchronizationActive()) {
  15. TransactionSynchronizationManager.registerSynchronization(
  16. new SpringSessionSynchronization(sessionHolder, this.sessionFactory, false));
  17. sessionHolder.setSynchronizedWithTransaction(true);
  18. // Switch to FlushMode.AUTO, as we have to assume a thread-bound Session
  19. // with FlushMode.MANUAL, which needs to allow flushing within the transaction.
  20. FlushMode flushMode = SessionFactoryUtils.getFlushMode(session);
  21. if (flushMode.equals(FlushMode.MANUAL) &&
  22. !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
  23. session.setFlushMode(FlushMode.AUTO);
  24. sessionHolder.setPreviousFlushMode(flushMode);
  25. }
  26. }
  27. return session;
  28. }
  29. else if (value instanceof EntityManagerHolder) {
  30. // JpaTransactionManager
  31. return ((EntityManagerHolder) value).getEntityManager().unwrap(Session.class);
  32. }
  33. if (this.transactionManager != null && this.jtaSessionContext != null) {
  34. try {
  35. if (this.transactionManager.getStatus() == Status.STATUS_ACTIVE) {
  36. Session session = this.jtaSessionContext.currentSession();
  37. if (TransactionSynchronizationManager.isSynchronizationActive()) {
  38. TransactionSynchronizationManager.registerSynchronization(
  39. new SpringFlushSynchronization(session));
  40. }
  41. return session;
  42. }
  43. }
  44. catch (SystemException ex) {
  45. throw new HibernateException("JTA TransactionManager found but status check failed", ex);
  46. }
  47. }
  48. if (TransactionSynchronizationManager.isSynchronizationActive()) {
  49. Session session = this.sessionFactory.openSession();
  50. if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
  51. session.setFlushMode(FlushMode.MANUAL);
  52. }
  53. SessionHolder sessionHolder = new SessionHolder(session);
  54. TransactionSynchronizationManager.registerSynchronization(
  55. new SpringSessionSynchronization(sessionHolder, this.sessionFactory, true));
  56. TransactionSynchronizationManager.bindResource(this.sessionFactory, sessionHolder);
  57. sessionHolder.setSynchronizedWithTransaction(true);
  58. return session;
  59. }
  60. else {
  61. throw new HibernateException("Could not obtain transaction-synchronized Session for current thread");
  62. }
  63. }
对比OpenSessionInViewFilter#doFilterInternal ,可以发现两者获取hibernate 的session的逻辑相似;
  1. HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
  2. throws ServletException, IOException {
  3. SessionFactory sessionFactory = lookupSessionFactory(request);
  4. boolean participate = false;
  5. WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  6. String key = getAlreadyFilteredAttributeName();
  7. if (TransactionSynchronizationManager.hasResource(sessionFactory)) {
  8. // Do not modify the Session: just set the participate flag.
  9. participate = true;
  10. }
  11. else {
  12. boolean isFirstRequest = !isAsyncDispatch(request);
  13. if (isFirstRequest || !applySessionBindingInterceptor(asyncManager, key)) {
  14. logger.debug("Opening Hibernate Session in OpenSessionInViewFilter");
  15. Session session = openSession(sessionFactory);
  16. SessionHolder sessionHolder = new SessionHolder(session);
  17. TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
  18. AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(sessionFactory, sessionHolder);
  19. asyncManager.registerCallableInterceptor(key, interceptor);
  20. asyncManager.registerDeferredResultInterceptor(key, interceptor);
  21. }
  22. }
  23. try {
  24. filterChain.doFilter(request, response);
  25. }
  26. finally {
  27. if (!participate) {
  28. SessionHolder sessionHolder =
  29. (SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
  30. if (!isAsyncStarted(request)) {
  31. logger.debug("Closing Hibernate Session in OpenSessionInViewFilter");
  32. SessionFactoryUtils.closeSession(sessionHolder.getSession());
  33. }
  34. }
  35. }
spring是如何管理hibernate的事务的?HibernateTransactionManager

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

HibernateTransactionManager extends AbstractPlatformTransactionManager……

其中获取事务的源码参见:AbstractPlatformTransactionManager#getTransaction
  1. public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
  2. Object transaction = doGetTransaction();
  3. // Cache debug flag to avoid repeated checks.
  4. boolean debugEnabled = logger.isDebugEnabled();
  5. if (definition == null) {
  6. // Use defaults if no transaction definition given.
  7. definition = new DefaultTransactionDefinition();
  8. }
  9. if (isExistingTransaction(transaction)) {
  10. // Existing transaction found -> check propagation behavior to find out how to behave.
  11. return handleExistingTransaction(definition, transaction, debugEnabled);
  12. }
  13. // Check definition settings for new transaction.
  14. if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
  15. throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
  16. }
  17. // No existing transaction found -> check propagation behavior to find out how to proceed.
  18. if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
  19. throw new IllegalTransactionStateException(
  20. "No existing transaction found for transaction marked with propagation 'mandatory'");
  21. }
  22. else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
  23. definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
  24. definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
  25. SuspendedResourcesHolder suspendedResources = suspend(null);
  26. if (debugEnabled) {
  27. logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
  28. }
  29. try {
  30. boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
  31. DefaultTransactionStatus status = newTransactionStatus(
  32. definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
  33. doBegin(transaction, definition);
  34. prepareSynchronization(status, definition);
  35. return status;
  36. }
  37. catch (RuntimeException | Error ex) {
  38. resume(null, suspendedResources);
  39. throw ex;
  40. }
  41. }
  42. else {
  43. // Create "empty" transaction: no actual transaction, but potentially synchronization.
  44. if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
  45. logger.warn("Custom isolation level specified but no actual transaction initiated; " +
  46. "isolation level will effectively be ignored: " + definition);
  47. }
  48. boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
  49. return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
  50. }
  51. }
其中dobegin进入HibernateTransactionManager#doBegin
  1. protected void doBegin(Object transaction, TransactionDefinition definition) {
  2. HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
  3. if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
  4. throw new IllegalTransactionStateException(
  5. "Pre-bound JDBC Connection found! HibernateTransactionManager does not support " +
  6. "running within DataSourceTransactionManager if told to manage the DataSource itself. " +
  7. "It is recommended to use a single HibernateTransactionManager for all transactions " +
  8. "on a single DataSource, no matter whether Hibernate or JDBC access.");
  9. }
  10. Session session = null;
  11. try {
  12. /*** 判断是否 open一个newSession 见下文说明
  13. txObject.hasSessionHolder() 一般应返回true,因为在filter中新建了
  14. txObject.getSessionHolder().isSynchronizedWithTransaction() 何时为true呢?
  15. 在try代码块的末尾会设置其为true: txObject.getSessionHolder().setSynchronizedWithTransaction(true);
  16. 有理由相信 可能是第1+n次进入此dobegin方法时候,如果是txObject中的SessionHolder属性为同一个,则这个属性为true.
  17. 那么什么时候第1+n次进入dobegin,却携带了相同的txObject呢?
  18. 参见:AbstractPlatformTransactionManager#
  19. DefaultTransactionStatus status = newTransactionStatus(
  20. definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
  21. doBegin(transaction, definition);
  22. *************************************/
  23. if (!txObject.hasSessionHolder() || txObject.getSessionHolder().isSynchronizedWithTransaction()) {
  24. Interceptor entityInterceptor = getEntityInterceptor();
  25. Session newSession = (entityInterceptor != null ?
  26. obtainSessionFactory().withOptions().interceptor(entityInterceptor).openSession() :
  27. obtainSessionFactory().openSession());
  28. if (logger.isDebugEnabled()) {
  29. logger.debug("Opened new Session [" + newSession + "] for Hibernate transaction");
  30. }
  31. txObject.setSession(newSession);
  32. }
  33. session = txObject.getSessionHolder().getSession();
  34. boolean holdabilityNeeded = this.allowResultAccessAfterCompletion && !txObject.isNewSession();
  35. boolean isolationLevelNeeded = (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT);
  36. if (holdabilityNeeded || isolationLevelNeeded || definition.isReadOnly()) {
  37. if (this.prepareConnection && isSameConnectionForEntireSession(session)) {
  38. // We're allowed to change the transaction settings of the JDBC Connection.
  39. if (logger.isDebugEnabled()) {
  40. logger.debug("Preparing JDBC Connection of Hibernate Session [" + session + "]");
  41. }
  42. Connection con = ((SessionImplementor) session).connection();
  43. Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
  44. txObject.setPreviousIsolationLevel(previousIsolationLevel);
  45. if (this.allowResultAccessAfterCompletion && !txObject.isNewSession()) {
  46. int currentHoldability = con.getHoldability();
  47. if (currentHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT) {
  48. txObject.setPreviousHoldability(currentHoldability);
  49. con.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
  50. }
  51. }
  52. }
  53. else {
  54. // Not allowed to change the transaction settings of the JDBC Connection.
  55. if (isolationLevelNeeded) {
  56. // We should set a specific isolation level but are not allowed to...
  57. throw new InvalidIsolationLevelException(
  58. "HibernateTransactionManager is not allowed to support custom isolation levels: " +
  59. "make sure that its 'prepareConnection' flag is on (the default) and that the " +
  60. "Hibernate connection release mode is set to 'on_close' (the default for JDBC).");
  61. }
  62. if (logger.isDebugEnabled()) {
  63. logger.debug("Not preparing JDBC Connection of Hibernate Session [" + session + "]");
  64. }
  65. }
  66. }
  67. if (definition.isReadOnly() && txObject.isNewSession()) {
  68. // Just set to MANUAL in case of a new Session for this transaction.
  69. session.setFlushMode(FlushMode.MANUAL);
  70. // As of 5.1, we're also setting Hibernate's read-only entity mode by default.
  71. session.setDefaultReadOnly(true);
  72. }
  73. if (!definition.isReadOnly() && !txObject.isNewSession()) {
  74. // We need AUTO or COMMIT for a non-read-only transaction.
  75. FlushMode flushMode = SessionFactoryUtils.getFlushMode(session);
  76. if (FlushMode.MANUAL.equals(flushMode)) {
  77. session.setFlushMode(FlushMode.AUTO);
  78. txObject.getSessionHolder().setPreviousFlushMode(flushMode);
  79. }
  80. }
  81. Transaction hibTx;
  82. // Register transaction timeout.
  83. int timeout = determineTimeout(definition);
  84. if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
  85. // Use Hibernate's own transaction timeout mechanism on Hibernate 3.1+
  86. // Applies to all statements, also to inserts, updates and deletes!
  87. hibTx = session.getTransaction();
  88. hibTx.setTimeout(timeout);
  89. hibTx.begin();
  90. }
  91. else {
  92. // Open a plain Hibernate transaction without specified timeout.
  93. hibTx = session.beginTransaction();
  94. }
  95. // Add the Hibernate transaction to the session holder.
  96. txObject.getSessionHolder().setTransaction(hibTx);
  97. // Register the Hibernate Session's JDBC Connection for the DataSource, if set.
  98. if (getDataSource() != null) {
  99. SessionImplementor sessionImpl = (SessionImplementor) session;
  100. // The following needs to use a lambda expression instead of a method reference
  101. // for compatibility with Hibernate ORM <5.2 where connection() is defined on
  102. // SessionImplementor itself instead of on SharedSessionContractImplementor...
  103. ConnectionHolder conHolder = new ConnectionHolder(() -> sessionImpl.connection());
  104. if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
  105. conHolder.setTimeoutInSeconds(timeout);
  106. }
  107. if (logger.isDebugEnabled()) {
  108. logger.debug("Exposing Hibernate transaction as JDBC [" + conHolder.getConnectionHandle() + "]");
  109. }
  110. TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
  111. txObject.setConnectionHolder(conHolder);
  112. }
  113. // Bind the session holder to the thread.
  114. if (txObject.isNewSessionHolder()) {
  115. TransactionSynchronizationManager.bindResource(obtainSessionFactory(), txObject.getSessionHolder());
  116. }
  117. txObject.getSessionHolder().setSynchronizedWithTransaction(true);
  118. }
  119. catch (Throwable ex) {
  120. if (txObject.isNewSession()) {
  121. try {
  122. if (session != null && session.getTransaction().getStatus() == TransactionStatus.ACTIVE) {
  123. session.getTransaction().rollback();
  124. }
  125. }
  126. catch (Throwable ex2) {
  127. logger.debug("Could not rollback Session after failed transaction begin", ex);
  128. }
  129. finally {
  130. SessionFactoryUtils.closeSession(session);
  131. txObject.setSessionHolder(null);
  132. }
  133. }
  134. throw new CannotCreateTransactionException("Could not open Hibernate Session for transaction", ex);
  135. }
  136. }

如上 :若 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
  1. protected Object doGetTransaction() {
  2. HibernateTransactionObject txObject = new HibernateTransactionObject();
  3. txObject.setSavepointAllowed(isNestedTransactionAllowed());
  4. //获取 sessionFactory, 来自当前HibernateTransactionManager实例的sessionFactory属性
  5. SessionFactory sessionFactory = obtainSessionFactory();
  6. //获取当前线程绑定的sessionHolder,来自TransactionSynchronizationManager 的ThreadLocal<Map<Object, Object>> resources
  7. SessionHolder sessionHolder, =
  8. (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
  9. if (sessionHolder != null) {
  10. if (logger.isDebugEnabled()) {
  11. logger.debug("Found thread-bound Session [" + sessionHolder.getSession() + "] for Hibernate transaction");
  12. }
  13. txObject.setSessionHolder(sessionHolder);
  14. }
  15. else if (this.hibernateManagedSession) {
  16. try {
  17. Session session = sessionFactory.getCurrentSession();
  18. if (logger.isDebugEnabled()) {
  19. logger.debug("Found Hibernate-managed Session [" + session + "] for Spring-managed transaction");
  20. }
  21. txObject.setExistingSession(session);
  22. }
  23. catch (HibernateException ex) {
  24. throw new DataAccessResourceFailureException(
  25. "Could not obtain Hibernate-managed Session for Spring-managed transaction", ex);
  26. }
  27. }
  28. if (getDataSource() != null) {
  29. ConnectionHolder conHolder = (ConnectionHolder)
  30. TransactionSynchronizationManager.getResource(getDataSource());
  31. txObject.setConnectionHolder(conHolder);
  32. }
  33. return txObject;
  34. }

追踪代码而知, 在新建事务的时候,若当前线程之前已经新建了事务,且进入了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进入源码, 暂时未追踪运行时进入事务源码的代码,且留个疑问, 有暇在外网搭建个环境的时候,再做补充吧.


发表评论

目录