没有@Transactional的Hibernate sessionFactory.getCurrentSession()

问题描述 投票:3回答:3

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

java spring hibernate orm transactions
3个回答
11
投票

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

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

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

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

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

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

并深入研究@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而你可能不想打开Pandora的盒子)由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,因为没有@Transactional注释,切入点没有执行会创建一个同步事务。 (有关详细信息,请参阅org.springframework.transaction.interceptor.TransactionInterceptor。)

因此,这将我们带回到我们的用例,即当我们只想对数据库执行PlatformTransactionManager时,我们不希望调用SELECT及其数据库事务代码的开销。实现这一目标的简单方法就是不要调用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);
        }
    }
}

这将允许你在没有myHibernateService.activeUrls()注释的情况下调用@Transactional,还有你想要通过myHibernateService.refreshUrls()PlatformTransactionManager

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

无论如何,您不想使用上面的代码。相反,你可能想要使用@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时它会中断。是的,我仍然对此感到痛苦。)


0
投票

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

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


0
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.