Spring @Transactional方法引发异常时Hibernate merge()的行为

Spring @Transactional方法引发异常时Hibernate merge()的行为,spring,hibernate,jpa,transactional,Spring,Hibernate,Jpa,Transactional,我想问一下这种行为的原因,因为当运行到Spring@Transactional方法/类时,我似乎不完全理解Hibernate中的persist()和merge()之间的区别 我有下面的代码应该回滚DB操作,但它没有(整个类被注释为@Transactional): 引发异常时,以下代码不会按预期回滚: @Override public MyBean assignNewFoo(Integer id, Integer idNewFoo) { MyBean bean = myBeanRepos

我想问一下这种行为的原因,因为当运行到Spring
@Transactional
方法/类时,我似乎不完全理解Hibernate中的
persist()
merge()
之间的区别

我有下面的代码应该回滚DB操作,但它没有(整个类被注释为
@Transactional
):

引发异常时,以下代码不会按预期回滚:

@Override
public MyBean assignNewFoo(Integer id, Integer idNewFoo) {

    MyBean bean = myBeanRepository.findOne(id);
    myBeanRepository.save(bean);

    bean.setNewFoo(
            fooManagement.findById(idNewFoo)
            );
    if (true) throw new RuntimeException();
    return bean;
}
save()方法来自类
org.springframework.data.jpa.repository.support.SimpleParepository
,因此其代码为:

@Transactional
public <S extends T> S save(S entity) {

    if (entityInformation.isNew(entity)) {
        em.persist(entity);
        return entity;
    } else {
        return em.merge(entity);
    }
}
  • 您似乎误解了合并语义和容器管理的事务行为。assignNewFoo方法是事务性的,您的“bean”实例是从存储库加载的。正因为如此,“bean”实例一直处于管理状态,直到事务结束(或者直到您手动从持久性上下文中删除)。这意味着
    myBeanRepository.save(bean)调用不执行任何操作,因为“bean”已经是JPA管理的实体
    myBeanRepository.save(bean)==bean
    只要在中发出的同一事务“findOne”中执行保存,则该值将为真。合并用于将对实体的非托管实例所做的更改应用于托管实例。此代码说明案例合并用于:

    MyBean bean = repo.findOne(id);
    MyBean anotherInstance = new MyBean();
    anotherInstance.setId(id);
    anotherInstance.setNewFoo("100");
    MyBean managed = repo.save(anotherInstance);
    // And now we take a look:
    managed == bean; // => true
    anotherInstance == managed; // => false
    bean.getNewFoo(); // => "100"
    // An anotherInstance is still detached while save() call has 
    // returned us a managed instance ('bean')
    
    根据您引用的JPA规范条目:此处不适用。它说明了非事务性搜索,但您的搜索是在由
    assignNewFoo
    调用启动的事务中执行的

  • 从上面所写的所有内容来看:您提供的两个用于演示无回滚行为的代码示例实际上是相同的。您可能会面临您抱怨的问题,原因如下:

    • 您正在从@Transactional方法调用
      assignNewFoo
      ,并在这个外部@Transactional方法中执行事务应用程序检查。由于您的传播级别是“必需”的,并且在
      assignNewFoo
      调用中未捕获到RuntimeException,因此一旦
      assignNewFoo
      调用完成,事务将被标记为回滚,但实际回滚将在您的事务从方法传播完成后执行
    • 如果您100%确定自己做的一切都是对的,那么这可能是Spring/Provider/DBMS的问题。我无法在最新的Spring Boot+Hibernate 4+HSQLDB上重现这个bug,如果您没有选择,可能值得检查一下
  • 您似乎误解了合并语义和容器管理的事务行为。assignNewFoo方法是事务性的,您的“bean”实例是从存储库加载的。正因为如此,“bean”实例一直处于管理状态,直到事务结束(或者直到您手动从持久性上下文中删除)。这意味着
    myBeanRepository.save(bean)调用不执行任何操作,因为“bean”已经是JPA管理的实体
    myBeanRepository.save(bean)==bean
    只要在中发出的同一事务“findOne”中执行保存,则该值将为真。合并用于将对实体的非托管实例所做的更改应用于托管实例。此代码说明案例合并用于:

    MyBean bean = repo.findOne(id);
    MyBean anotherInstance = new MyBean();
    anotherInstance.setId(id);
    anotherInstance.setNewFoo("100");
    MyBean managed = repo.save(anotherInstance);
    // And now we take a look:
    managed == bean; // => true
    anotherInstance == managed; // => false
    bean.getNewFoo(); // => "100"
    // An anotherInstance is still detached while save() call has 
    // returned us a managed instance ('bean')
    
    根据您引用的JPA规范条目:此处不适用。它说明了非事务性搜索,但您的搜索是在由
    assignNewFoo
    调用启动的事务中执行的

  • 从上面所写的所有内容来看:您提供的两个用于演示无回滚行为的代码示例实际上是相同的。您可能会面临您抱怨的问题,原因如下:

    • 您正在从@Transactional方法调用
      assignNewFoo
      ,并在这个外部@Transactional方法中执行事务应用程序检查。由于您的传播级别是“必需”的,并且在
      assignNewFoo
      调用中未捕获到RuntimeException,因此一旦
      assignNewFoo
      调用完成,事务将被标记为回滚,但实际回滚将在您的事务从方法传播完成后执行
    • 如果您100%确定自己做的一切都是对的,那么这可能是Spring/Provider/DBMS的问题。我无法在最新的Spring Boot+Hibernate 4+HSQLDB上重现这个bug,如果您没有选择,可能值得检查一下

  • 你能发布全班的代码吗?完成。你认为其他地方有什么问题吗,@AlanHay?你能把全班的代码都发出来吗?完成了。“你认为其他地方出了什么问题吗,@AlanHay?谢谢你的回答,Roman。”。这是非常有用的。我进一步调试并发现,
    PersistenceContextType
    实际上是扩展的,而不是我所想的事务,因此这是适用于
    findOne
    的文本:“[…]如果使用具有扩展持久性上下文的实体管理器,它们将被管理”,正如您所说的。大约2点。我无法再复制这个,所以我不能说什么。谢谢你的回复,罗曼。这是非常有用的。我进一步调试并发现,
    PersistenceContextType
    实际上是扩展的,而不是我所想的事务,因此这是适用于
    findOne
    的文本:“[…]如果使用具有扩展持久性上下文的实体管理器,它们将被管理”,正如您所说的。大约2点。我无法再次复制这一点,所以我不能说什么。
    MyBean bean = repo.findOne(id);
    MyBean anotherInstance = new MyBean();
    anotherInstance.setId(id);
    anotherInstance.setNewFoo("100");
    MyBean managed = repo.save(anotherInstance);
    // And now we take a look:
    managed == bean; // => true
    anotherInstance == managed; // => false
    bean.getNewFoo(); // => "100"
    // An anotherInstance is still detached while save() call has 
    // returned us a managed instance ('bean')