【问题标题】:Hibernate sessionFactory.getCurrentSession() without @Transactional没有 @Transactional 的休眠 sessionFactory.getCurrentSession()
【发布时间】:2015-06-23 18:37:12
【问题描述】:

在 Hibernate4、Spring4 中 我想使用没有@Transactional 注释的sessionFactory.getCurrentSession()。有什么办法吗?

【问题讨论】:

    标签: java spring hibernate orm transactions


    【解决方案1】:

    您可以在不声明 explicit transaction boundary 的情况下使用 Hibernate,但您只能发出 SELECT 语句,因为 DML 语句需要事务。

    【讨论】:

      【解决方案2】:

      简单的答案是:是的,你当然可以,因为SessionFactory.getCurrentSession() 只是一个接口的方法,所以你可以编写你自己的实现类,给你任何你喜欢的Session

      但是,这可能不是您要寻找的答案。

      我们一直在问自己一个类似的问题:为什么在将 Hibernate 与 Spring 的事务管理一起使用时,我们必须将@Transactional 添加到我们所有的方法中,即使是只有SELECT 数据的方法,因此不需要在数据库事务的上下文中执行?

      这个问题的答案并不那么简单,但让我们看一下所涉及的一些管道,看看我们是否能理解它。

      首先,正如在 SO 的其他地方所提到的,Session 的概念与事务的概念有根本的联系。 Session 接口的 javadoc 中有提示:

      Session 的生命周期以逻辑事务的开始和结束为界。 (长事务可能跨越多个数据库事务。)

      并深入研究@Transactional 类的 javadoc 确认其目的是指示何时应在“事务上下文”中执行代码,这不一定 的上下文>数据库事务。

      这也解释了为什么 Spring 的 @Transactional 注释允许您设置属性 readOnly=true,但稍后会详细介绍。

      回到 Spring4 和 Hibernate4,当你调用 sessionFactory.getCurrentSession() 时,它实际上会在 SessionFactoryImpl 中执行以下代码:

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

      所以它实际上推迟到 CurrentSessionContext 的实现(除非您使用 JTA 并且您可能不想打开那个潘多拉魔盒)由 SpringSessionContext 类处理:

      @Override
      public Session currentSession() throws HibernateException {
          Object value = TransactionSynchronizationManager.getResource(this.sessionFactory);
          if (value instanceof Session) {
              return (Session) value;
          }
          else if (value instanceof SessionHolder) {
              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 = session.getFlushMode();
                  if (flushMode.equals(FlushMode.MANUAL) &&
                          !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
                      session.setFlushMode(FlushMode.AUTO);
                      sessionHolder.setPreviousFlushMode(flushMode);
                  }
              }
              return session;
          }
      
          if (this.transactionManager != 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");
          }
      }
      

      并解释为什么您会看到异常:

      无法获取当前线程的事务同步会话

      当您在未使用 @Transactional 注释的方法中调用 sessionFactory.getCurrentSession() 时,TransactionSynchronizationManager.isSynchronizationActive() 将返回 false,因为没有 @Transactional 注释,切入点尚未执行,这将创建一个同步事务。 (有关更多信息,请参阅org.springframework.transaction.interceptor.TransactionInterceptor。)

      所以这将我们带回到我们的用例,即当我们只想对数据库执行SELECT 时,我们不希望调用PlatformTransactionManager 和它的数据库事务代码的开销。实现这一点的简单方法就是不调用sessionFactory.getCurrentSession(),而是直接打开Session。以这个 Spring 托管代码为例:

      public class MyHibernateService {   
      
          @Autowired
          private SessionFactory sessionFactory;  
      
          protected Session transactionalSession() {  
              return sessionFactory.getCurrentSession();
          }
      
          protected Session readOnlySession() {
              if(TransactionSynchronizationManager.isSynchronizationActive())
                  return transactionalSession();
              Session session = this.sessionFactory.openSession();
              session.setFlushMode(FlushMode.MANUAL);
              return session;
          }
      
          public List<SalixUrl> activeUrls() {
              return readOnlySession().createCriteria(SalixUrl.class)
                      .add(Restrictions.gt("published", LocalDateTime.now()))
                      .add(Restrictions.lt("removed", LocalDateTime.now()))
                      .list();
          }
      
          @Transactional
          public List<SalixUrl> refreshUrls() {
              List<SalixUrl> urls = activeUrls();
              for(SalixUrl url : urls) {
                  url.setLastChecked(LocalDateTime.now());
                  transactionalSession().update(url);
              }
          }
      }
      

      这将允许您在没有@Transactional 注释的情况下调用myHibernateService.activeUrls(),还可以调用myHibernateService.refreshUrls(),您希望通过PlatformTransactionManager

      如果这段代码看起来很眼熟,可能是因为您查看了OpenSessionInViewFilter(或拦截器)的源代码,它通常用于缓解LazyLoadingExceptions,也负责很多n+ 1 程序员认为他们正在通过使用FetchType.LAZY 来定义实体关系来优化他们的 ORM 模型但没有编码他们的服务/存储库层来实际获取需要获取的内容以生成视图时的问题。

      无论如何,你不想使用上面的代码。相反,您可能想使用 @Transactional 注释并让 Spring 和 Hibernate 框架决定实际需要哪种类型的数据库事务。

      如果您担心性能,那么您有几个选择:

      不是 1。您可以使用 Spring 的 @Transactional(readOnly=true),但请注意,这不一定是个好主意。我不提倡使用 javax @Transactional,因为它更通用 - 如果您已将颜色绑定到 Spring 桅杆,那么您不妨使用它所提供的东西。相反,我很谨慎,因为它所做的一切(使用当前实现)是请求将来自底层数据库提供程序的Connection 对象标记为只读。由于几个原因,这可能会出现问题。

      一方面,您的数据库提供程序可能不支持只读连接(例如用于 MSSQL 服务器的 jTDS JDBC 驱动程序),因此它可能毫无意义。

      第二个原因是连接池。如果你正在使用一个支持只读连接的数据库,比如 PostgreSQL,和一个连接池(比如 C3P0),那么你并不想将某些连接标记为只读,然后将它们返回到池中,然后允许在您需要执行数据库写入的情况下将它们提供回来。 (我还没有用 Hibernate4 和 Spring4 测试过,但肯定是 Spring3、Hibernate3 和 C3P0 的问题。)

      2. 使用缓存。对于我们这些天可以使用的硬件,缓存可能是答案,并且您有很多可用的选项。您可以为 Hibernate 实体配置二级缓存,并且 Spring 本身有一个很好的 spring-cache 模块,它允许缓存服务/存储库方法 - 看看如何集成 EhCache。

      3. 使用 JDBC 或其他方式编写您自己的数据库查询。 Gavin King(Hibernate 的作者)很长一段时间以来一直在指出,仅仅因为您将 Hibernate 用于 ORM,您就不必在所有事情上都使用它:https://plus.google.com/+GavinKing/posts/LGJU1NorAvY(我找不到他所说的明确引用“不要将 Hibernate 用于高性能 SELECT" 但我认为几年前我读过一些东西)。

      但还有两个更重要的问题:

      没有 1。不应该担心性能。如果你需要,那么你不应该阅读这篇文章,因为你应该已经知道所有这些了 ;-) - 但是忽略我的滑稽,不要浪费时间进行原子代码优化,相反,你需要像工程师一样行事将您的系统作为一个整体(如 Dirk Gently),然后判断使您的系统尽可能高性能的最有效方法。 记住:协和飞机不再飞行有几个原因。

      否 2。 您可能不再需要使用 SessionFactory。 JPA 2 和EntityManager 旨在使SessionFactory 的显式使用变得不必要。甚至 Emmanuel Bernard(另一位 Hibernate 的作者)在几年前也给了我们这个建议:http://www.theserverside.com/news/2240186700/The-JPA-20-EntityManager-vs-the-Hibernate-Session-Which-one-to-use

      但你知道吗:我喜欢 SessionFactory 和 Hibernate Criteria API 以及与之相关的一切。所以我会继续使用它,直到他们弃用 Spring 框架对它的支持。因为,正如我所说,如果您已将颜色固定在框架桅杆上,那么您不妨使用框架提供的所有功能。实际上,抽象的主要好处(您可以替换底层 ORM 或数据库提供程序)是您可能永远不必担心的事情。

      (但是,是的,我也去过那里,我也做过——我不得不将中型代码库从 MSSQL 迁移到 PostgreSQL,最大的问题不是 Spring/ORM 层,而是特定于数据库诸如存储过程和触发器之类的代码。事实上,以前的开发人员试图通过使用 @Transactional(readOnly=true) 来优化查询,但不了解 MSSQL 实际上并不支持它,并且当您使用 PostgreSQL 和 C3P0 时它会中断。是的,我仍然对此感到很痛苦。)

      【讨论】:

      • 我见过的最好的答案...你能帮我解决这个问题stackoverflow.com/questions/38823693/…吗?我一直在寻找解决方案,但还没有找到...
      • 他,我确定我已经在事务上下文之外进行了查询,并且当时是使用 entitymanager。现在我目前陷入休眠状态,所以我不能:(
      【解决方案3】:

      使用注解是实现声明式事务管理的一种方式,但不是唯一的方式。您也可以在 xml 配置中使用 txaop 命名空间。这样,您就有了一个集中的事务配置,您还可以在其中使用通配符进行方法匹配。 您可以以同样的方式使用sessionFactory.getCurrentSession()。只是事务划分样式发生了变化。

      更多详情请参阅Spring reference documentation

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-12-31
        • 2017-08-04
        • 1970-01-01
        • 2015-11-09
        • 2011-08-06
        • 2014-11-25
        • 2013-02-28
        • 1970-01-01
        相关资源
        最近更新 更多