Spring @Transactional(Propagation.NEVER)应该创建hibernate会话吗?

时间:2016-07-13 11:59:18

标签: java spring hibernate jpa

让我们假设我们在spring(4.2.7)中正确配置了由hibernate(4.3.11)支持的jpa。启用了Hibernate一级缓存。我们使用声明式交易。 我们有OuterBean

@Service
public class OuterBean {

    @Resource
    private UserDao userDao;

    @Resource
    private InnerBean innerBean;

    @Transactional(propagation = Propagation.NEVER)
    public void withoutTransaction(){
        User user = userDao.load(1l);
        System.out.println(user.getName());//return userName
        innerBean.withTransaction();
        user = userDao.load(1l);
        System.out.println(user.getName());//return userName instead of newUserName
    }

}

从OuterBean调用的InnerBean:

@Service
public class InnerBean {

    @Resource
    private UserDao userDao;

    @Transactional
    public void withTransaction(){
        User user = userDao.load(1l);
        user.setName("newUserName");
    }

}

OuterBean中的方法user.getName()两次返回相同的值(第二次是在数据库中的更新名称之后)是否正确?

换句话说,@Transactional(propagation = Propagation.NEVER)为方法withoutTransaction()创建hibernate会话的正确行为导致第二次调用user.getName()从休眠的第一级缓存而不是数据库中读取?

修改

为了更好地解释问题,我从hibernate会话的创建中附加了跟踪

TRACE org.hibernate.internal.SessionFactoryImpl$SessionBuilderImpl  - Opening Hibernate Session.  tenant=null, owner=org.hibernate.jpa.internal.EntityManagerImpl@c17285e
TRACE org.hibernate.internal.SessionImpl  - Opened session at timestamp: 14689173439
TRACE org.hibernate.internal.SessionImpl  - Setting flush mode to: AUTO
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
userName
TRACE org.hibernate.internal.SessionFactoryImpl$SessionBuilderImpl  - Opening Hibernate Session.  tenant=null, owner=org.hibernate.jpa.internal.EntityManagerImpl@715c48ca
TRACE org.hibernate.internal.SessionImpl  - Opened session at timestamp: 14689173439
TRACE org.hibernate.internal.SessionImpl  - Setting flush mode to: AUTO
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Automatically flushing session
TRACE org.hibernate.internal.SessionImpl  - before transaction completion
TRACE org.hibernate.internal.SessionImpl  - after transaction completion
TRACE org.hibernate.internal.SessionImpl  - Closing session
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
userName
TRACE org.hibernate.internal.SessionImpl  - Closing session

现在让我们在删除@Transactional(propagation = Propagation.NEVER)

时比较跟踪
TRACE org.hibernate.internal.SessionFactoryImpl$SessionBuilderImpl  - Opening Hibernate Session.  tenant=null, owner=org.hibernate.jpa.internal.EntityManagerImpl@4ebd2c5f
TRACE org.hibernate.internal.SessionImpl  - Opened session at timestamp: 14689203905
TRACE org.hibernate.internal.SessionImpl  - Setting flush mode to: AUTO
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Closing session
userName
TRACE org.hibernate.internal.SessionFactoryImpl$SessionBuilderImpl  - Opening Hibernate Session.  tenant=null, owner=org.hibernate.jpa.internal.EntityManagerImpl@5af84083
TRACE org.hibernate.internal.SessionImpl  - Opened session at timestamp: 14689203905
TRACE org.hibernate.internal.SessionImpl  - Setting flush mode to: AUTO
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Automatically flushing session
TRACE org.hibernate.internal.SessionImpl  - before transaction completion
TRACE org.hibernate.internal.SessionImpl  - after transaction completion
TRACE org.hibernate.internal.SessionImpl  - Closing session
TRACE org.hibernate.internal.SessionFactoryImpl$SessionBuilderImpl  - Opening Hibernate Session.  tenant=null, owner=org.hibernate.jpa.internal.EntityManagerImpl@35f4f41f
TRACE org.hibernate.internal.SessionImpl  - Opened session at timestamp: 14689203906
TRACE org.hibernate.internal.SessionImpl  - Setting flush mode to: AUTO
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Closing session
newUserName

请注意,当我从userDao每次调用方法时,省略@Transactional(propagation = Propagation.NEVER)单独的会话是克里特岛。

所以我的问题也可以表述为 春天不应该@Transactional(propagation = Propagation.NEVER)作为监护人实施,以防止我们意外使用交易,没有任何副作用(会话创建)?

4 个答案:

答案 0 :(得分:14)

行为是正确的 - Hibernate将始终创建一个会话(您希望它如何执行任何操作?),并通过加载与该会话关联的实体。由于withoutTransaction未参与交易,因此withTransaction内所做的更改将在新交易中进行,并且除非您致电refresh,否则不应显示,这将强制重新启动 - 从数据库加载。

我引用Hibernate's official documentation

  

Session的主要功能是为映射的实体类的实例提供创建,读取和删除操作。实例可能存在以下三种状态之一:

     
      
  • 瞬态:从不持久,与任何会话无关
  •   
  • persistent:与唯一的Session分离关联:之前
  •   
  • 持久性,与任何会话无关
  •   
     

通过调用save()persist()saveOrUpdate(),可以使暂时性实例持久化。通过调用delete()可以使持久实例变为瞬态。 get()load()方法返回的任何实例都是持久的。

来自Christian Bauer,Gavin King和Gary Gregory的Java Persistence With Hibernate, Second Edition

  

持久化上下文充当第一级缓存;它会记住您在特定工作单元中处理的所有实体实例。例如,如果您要求Hibernate使用主键值(按标识符查找)加载实体实例,Hibernate可以首先检查持久化上下文中的当前工作单元。 如果Hibernate在持久化上下文中找到实体实例,则不会发生数据库命中 - 这是应用程序的可重复读取。具有相同持久性上下文的连续em.find(Item.class, ITEM_ID)调用将产生相同的结果。

同样来自Java Persistence With Hibernate, Second Edition

  

持久性上下文缓存始终打开 - 无法关闭它。它确保了以下内容:

     
      
  • 在对象图中循环引用的情况下,持久层不容易受到堆栈溢出的影响。
  •   
  • 在工作单元的末尾永远不会有相同数据库行的冲突表示。提供程序可以安全地将对实体实例所做的所有更改写入数据库。
  •   
  • 同样,在特定持久化上下文中所做的更改始终对该工作单元及其持久性上下文中执行的所有其他代码立即可见。 JPA保证可重复的实体实例读取。
  •   

关于交易,这里摘录自official Hibernate's documentation

  

定义从配置的基础事务管理方法中抽象应用程序的合同。允许应用程序定义工作单元,同时保持对底层事务实现的抽象(例如,JTA,JDBC)。

因此,总结一下,withTransactionwithoutTransaction将不共享UnitOfWork,因此不会共享第一级缓存,这就是第二次加载返回原始值的原因。

至于为什么这两种方法不共享工作单元的原因,你可以参考Shailendra的答案。

修改

你似乎误解了一些事情。必须始终创建一个会话 - 这是Hibernate的工作方式,周期。您对没有创建会话的期望等于期望在没有JDBC连接的情况下执行JDBC查询:)

两个示例之间的区别在于,使用@Transactional(propagation = Propagation.NEVER),您的方法被Spring拦截和代理,并且只为withoutTransaction中的查询创建了一个会话。删除注释时,会从Spring的事务拦截器中排除方法,因此将为每个与DB相关的操作创建一个新会话。我再说一遍,我不能强调这一点 - 你必须有一个开放的会话来执行任何查询。

就守卫而言 - 尝试使用withTransaction使用Propagation.NEVER和withoutTransaction使用默认的@Transactional注释来查看两种方法的注释,看看会发生什么(剧透:你会得到一个IllegalTransactionStateException

<强> EDIT2:

至于为什么会话在外部bean中的两个负载之间共享 - 这正是JpaTransactionManager应该做的事情,并且通过@Transactional注释你的方法你&#39;我告诉Spring它应该使用配置的事务管理器来包装你的方法。这是the official documentation关于JpaTransactionManager预期行为的说法:

  

单个JPA EntityManagerFactory的PlatformTransactionManager实现。 将JPA EntityManager从指定的工厂绑定到线程,可能允许每个工厂一个线程绑定的EntityManager 。 SharedEntityManagerCreator和@PersistenceContext知道线程绑定的实体管理器并自动参与此类事务。支持此事务管理机制的JPA访问代码需要使用其中任何一个。

另外,要了解Spring如何处理声明式事务管理(即方法上的@Transactional注释),请参阅official documentation。为了便于导航,我将包含一个引用:

  

关于Spring Framework的声明式事务支持,最重要的概念是通过AOP代理启用此支持,并且事务性建议由元数据驱动(当前基于XML或基于注释)。 AOP与事务元数据的组合产生一个AOP代理,该代理使用TransactionInterceptor和适当的PlatformTransactionManager实现来驱动方法调用周围的事务

答案 1 :(得分:2)

@Transactional(propagation = Propagation.NEVER)仍会创建一个会话。如果您将Spring / Hibernate / JPA组合用于非分布式事务,那么您肯定使用JpaTransactionManager作为Spring 交易经理。你的问题的答案在于这堂课。一个好主意是在IDE中使用调试器来跟踪发生的事情。此类的doBegin方法(由Spring事务基础结构调用: -

protected void doBegin(Object transaction, TransactionDefinition definition) {
        JpaTransactionObject txObject = (JpaTransactionObject) transaction;

        if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
            throw new IllegalTransactionStateException(
                    "Pre-bound JDBC Connection found! JpaTransactionManager does not support " +
                    "running within DataSourceTransactionManager if told to manage the DataSource itself. " +
                    "It is recommended to use a single JpaTransactionManager for all transactions " +
                    "on a single DataSource, no matter whether JPA or JDBC access.");
        }

        try {
            if (txObject.getEntityManagerHolder() == null ||
                    txObject.getEntityManagerHolder().isSynchronizedWithTransaction()) {
                EntityManager newEm = createEntityManagerForTransaction();
                if (logger.isDebugEnabled()) {
                    logger.debug("Opened new EntityManager [" + newEm + "] for JPA transaction");
                }
                txObject.setEntityManagerHolder(new EntityManagerHolder(newEm), true);
            }

            EntityManager em = txObject.getEntityManagerHolder().getEntityManager();

            // Delegate to JpaDialect for actual transaction begin.
            final int timeoutToUse = determineTimeout(definition);
            Object transactionData = getJpaDialect().beginTransaction(em,
                    new DelegatingTransactionDefinition(definition) {
                        @Override
                        public int getTimeout() {
                            return timeoutToUse;
                        }
                    });
            txObject.setTransactionData(transactionData);

            // Register transaction timeout.
            if (timeoutToUse != TransactionDefinition.TIMEOUT_DEFAULT) {
                txObject.getEntityManagerHolder().setTimeoutInSeconds(timeoutToUse);
            }

            // Register the JPA EntityManager's JDBC Connection for the DataSource, if set.
            if (getDataSource() != null) {
                ConnectionHandle conHandle = getJpaDialect().getJdbcConnection(em, definition.isReadOnly());
                if (conHandle != null) {
                    ConnectionHolder conHolder = new ConnectionHolder(conHandle);
                    if (timeoutToUse != TransactionDefinition.TIMEOUT_DEFAULT) {
                        conHolder.setTimeoutInSeconds(timeoutToUse);
                    }
                    if (logger.isDebugEnabled()) {
                        logger.debug("Exposing JPA transaction as JDBC transaction [" +
                                conHolder.getConnectionHandle() + "]");
                    }
                    TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
                    txObject.setConnectionHolder(conHolder);
                }
                else {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Not exposing JPA transaction [" + em + "] as JDBC transaction because " +
                                "JpaDialect [" + getJpaDialect() + "] does not support JDBC Connection retrieval");
                    }
                }
            }

            // Bind the entity manager holder to the thread.
            if (txObject.isNewEntityManagerHolder()) {
                TransactionSynchronizationManager.bindResource(
                        getEntityManagerFactory(), txObject.getEntityManagerHolder());
            }
            txObject.getEntityManagerHolder().setSynchronizedWithTransaction(true);
        }

        catch (TransactionException ex) {
            closeEntityManagerAfterFailedBegin(txObject);
            throw ex;
        }
        catch (Throwable ex) {
            closeEntityManagerAfterFailedBegin(txObject);
            throw new CannotCreateTransactionException("Could not open JPA EntityManager for transaction", ex);
        }
    }

使用JPA时的事务资源实际上是实体管理器(底层实现是hibernate中的会话),你可以看到这是这个方法的第一件事

EntityManager em = txObject.getEntityManagerHolder().getEntityManager();

所以肯定会创建一个实体管理器/会话。然后,事务属性通过TransactionDefinition传递给底层JpaDialect(HibernateJpaDialect)。该类实际上实际上获得了底层的Hibernate Session和会话的事务API。

HibernateJpaDialect {
........
public Object beginTransaction(EntityManager entityManager, TransactionDefinition definition)
Session session = getSession(entityManager);
entityManager.getTransaction().begin();
......
......
}
......

答案 2 :(得分:2)

首先,当您在JPA API后面使用hibernate时,我将使用术语EntityManager而不是会话(严格来说,只是术语问题)。

使用JPA对数据库的每次访问都涉及EntityManager,您正在获取实体,需要EntityManager(EM)。所谓的第一级缓存只不过是EM管理实体的状态。

理论上,EM的生命周期很短并且与工作单元绑定(因此通常用于交易,请参阅Struggling to understand EntityManager proper use)。

现在可以以不同的方式使用JPA:容器管理或用户管理的持久性。当EM由容器管理时(你的情况,这里spring是容器),最后负责管理EM范围/生命周期(为你创建,刷新和销毁它)。由于EM受限于事务/工作单元,因此该任务被委托给TransactionManager(处理@Transactional注释的对象)。

当您使用@Transactional(propagation = Propagation.NEVER)注释方法时,您正在创建一个spring逻辑事务范围,该范围将确保没有绑定到最终现有EM的现有基础JDBC事务,该事务不会创建一个并将使用JDBC自动提交模式,如果不存在,则为此逻辑事务范围创建EM。

关于在未定义事务逻辑范围时为每个DAO调用创建新EM实例的事实,您必须记住,您无法使用EM之外的JPA访问数据库。在这种情况下,AFAIK hibernate用于抛出no session bound to thread错误,但这可能是在以后的版本中发展的,否则您的DAO可能会注释@Transactional(propagation = Propagation.SUPPORT),如果不存在封闭的逻辑范围,它也会自动创建EM。这是一种不好的做法,因为交易应该在工作单元中定义,例如。服务水平,而不是DAO。

答案 3 :(得分:1)

我不认为这是正确的行为。同事们说的是,即使没有事务,hibernate也会创建一个会话。但这意味着我们正面临两个会话S1和S2,分别来自DAO的两个读取。同时L1缓存始终是每个会话,因此对于L1缓存命中两个单独的会话没有意义。看起来你的Spring不尊重@Transactional(propagation = Propagation.NEVER)

@Transactional(propagation = Propagation.NEVER)应该等同于你只是从main方法初始化你的服务并自己随后调用DAO。

在主类中试一试,看看它会如何反应。我怀疑它会再次击中L1缓存。

此外,我将复制粘贴来自Sprint的文档,从不传播:

  

从不非事务性执行,如果是事务则抛出异常   存在。

还有一个问题 - 是否将hibernate配置为AutoCommit。是否有可能&#34; runInTransaction&#34; - 方法没有提交?