没有@Transactional的Hibernate sessionFactory.getCurrentSession()

时间:2015-04-17 04:20:53

标签: java spring hibernate orm transactions

在Hibernate4中,Spring4 我想在没有sessionFactory.getCurrentSession()注释的情况下使用@Transactional。有没有办法做到这一点?

3 个答案:

答案 0 :(得分:11)

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

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

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

对此的答案并非如此简单,但让我们看一下所涉及的管道,看看我们是否能理解它。

首先,正如SO在其他地方所提到的,SELECT的概念从根本上与交易的概念有关。在Session界面的javadoc中有一个提示:

  

会话的生命周期受逻辑事务的开始和结束的限制。 (长事务可能跨越多个数据库事务。)

并深入研究Session类的javadoc,确认它的目的是指示何时应该在"事务上下文中执行代码",这不是必须 数据库事务的上下文。

这也解释了为什么Spring的@Transactional注释允许您设置属性@Transactional,但稍后会更多。

回到Spring4和Hibernate4,当你调用readOnly=true时,它实际执行sessionFactory.getCurrentSession()中的以下代码:

SessionFactoryImpl

所以它实际上推迟了public Session getCurrentSession() throws HibernateException { if ( currentSessionContext == null ) { throw new HibernateException( "No CurrentSessionContext configured!" ); } return currentSessionContext.currentSession(); } 的实现(除非您使用JTA并且您可能不想打开Pandora的盒子)由CurrentSessionContext类处理:

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"); } } 注释的方法中调用sessionFactory.getCurrentSession()时,@Transactional会返回TransactionSynchronizationManager.isSynchronizationActive(),因为如果没有false注释,则切入点没有&#39 ; t执行会创建同步事务。 (有关详细信息,请参阅@Transactional。)

因此,这将我们带回到我们的用例,即当我们只想执行一个时,我们不希望调用org.springframework.transaction.interceptor.TransactionInterceptor及其数据库事务代码的开销。 PlatformTransactionManager针对数据库。实现此目的的简单方法就是不要调用SELECT而只是明确地打开sessionFactory.getCurrentSession()。例如,使用这个Spring托管代码:

Session

允许您在没有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); } } } 注释的情况下调用myHibernateService.activeUrls(),但也可以@Transactional调用myHibernateService.refreshUrls()

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

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

如果你担心表现,那么你有几个选择:

否1。您可以使用Spring FetchType.LAZY,但请注意,这不是必然一个好主意。我并不主张使用javax @Transactional,因为它更通用 - 如果你将你的颜色与Spring桅杆绑在一起,那么你也可以使用它所提供的东西。相反,我很谨慎,因为它所做的一切(使用当前实现)是请求将底层数据库提供程序中的@Transactional(readOnly=true)对象标记为只读。由于几个原因,这可能是有问题的。

首先,您的数据库提供程序可能不支持只读连接(例如,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(我无法找到明确引用他所说的&#34;不要使用Hibernate进行表演@Transactional&#34;但我认为我几年前读过的东西。)

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

否1。不应该担心性能。如果你需要那么你不应该读这个,因为你应该已经知道了所有这些;-) - 但是忽略了我的讽刺,不要浪费时间进行原子代码优化,而是你需要表现得像一个工程师,整个看你的系统(如Dirk Gently),然后判断最有效的方法,使你的系统尽可能高效。 请记住:为什么协和会不再飞行有几个原因。

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

但是你知道:我喜欢EntityManager和Hibernate Criteria API以及随之而来的一切。所以我要继续使用它,直到他们从Spring框架中弃用对它的支持。因为,正如我已经说过的,如果你已经将你的颜色钉在框架桅杆上,那么你也可以使用框架所提供的所有功能。实际上,抽象的主要好处(你可以换掉底层的ORM或数据库提供者)是你可能永远不必担心的事情。

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

答案 1 :(得分:0)

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

答案 2 :(得分:0)

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

有关详细信息,请参阅Spring reference documentation