@Transactional 方法抛出异常时 Hibernate merge() 的行为

Behaviour of Hibernate merge() when @Transactional method throws an exception

我想问一下这种行为的原因,因为当 运行 变成 [=52= 时,我似乎没有完全理解 Hibernate 中 persist()merge() 之间的区别] @Transactional methods/classes.

我有以下代码应该回滚数据库操作但它没有(整个 class 被注释为 @Transactional):

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

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

    bean.setNewFoo(
            fooManagement.findById(idNewFoo)
            );
    if (true) throw new RuntimeException();
    return bean;
}

以下代码在抛出异常时按预期进行回滚:

@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()方法来自classorg.springframework.data.jpa.repository.support.SimpleJpaRepository,所以它的代码是:

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

    if (entityInformation.isNew(entity)) {
        em.persist(entity);
        return entity;
    } else {
        return em.merge(entity);
    }
}

该实体是现有实体,所以我知道它在做 merge()。根据 JPA specification:

The find method (provided it is invoked without a lock or invoked with LockModeType.NONE) and the getReference method are not required to be invoked within a transaction context. If an entity manager with transaction-scoped persistence context is in use, the resulting entities will be detached; if an entity manager with an extended persistence context is used, they will be managed.


The merge operation allows for the propagation of state from detached entities onto persistent entities managed by the entity manager. The semantics of the merge operation applied to an entity X are as follows:

  • If X is a detached entity, the state of X is copied onto a pre-existing managed entity instance X' of the same identity or a new managed copy X' of X is created.
  • If X is a new entity instance, a new managed entity instance X' is created and the state of X is copied into the new managed entity instance X'.
  • If X is a removed entity instance, an IllegalArgumentException will be thrown by the merge operation (or the transaction commit will fail).
  • If X is a managed entity, it is ignored by the merge operation, however, the merge operation is cascaded to entities referenced by relationships from X if these relationships have been annotated with the cascade element value cascade=MERGE or cascade=ALL annotation.
  • For all entities Y referenced by relationships from X having the cascade element value cascade=MERGE or cascade=ALL, Y is merged recursively as Y'. For all such Y referenced by X, X' is set to reference Y'. (Note that if X is managed then X is the same object as X'.)
  • If X is an entity merged to X', with a reference to another entity Y, where cascade=MERGE or cascade=ALL is not specified, then navigation of the same association from X' yields a reference to a managed object Y' with the same persistent identity as Y.
  1. 如果 merge() 返回的副本应该是托管实体,为什么当我使用分离实体时更改存储在数据库中? (除非有例外。这就是我想要的行为)

  2. 如果我修改新的托管实体但抛出异常,为什么仍然会提交更改?

编辑 根据@alan-hay 的要求:

package org.customer.somefoos.service.impl;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Resource;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import org.customer.somefoos.entity.MyBean;
import org.customer.somefoos.repository.MyBeanRepository;
import org.customer.somefoos.service.MyBeanManagement;
import org.customer.somefoos.service.FooManagement;

@Service
@Transactional
public class MyBeanManagementImpl implements MyBeanManagement {

    @Resource
    private MyBeanRepository myBeanRepository;

    @Resource
    private FooManagement fooManagement;


    @Override
    public List<MyBean> findAll() {
        return myBeanRepository.findAll();
    }

    @Override
    public MyBean findById(Integer id) {
        return myBeanRepository.findOne(id);
    }

    @Override
    public void delete(Integer id) {
        myBeanRepository.delete(id);
    }

    @Override
    public MyBean save(MyBean myBean) { 
        return myBeanRepository.save(myBean);
    }

    @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;
    }

}
  1. 看来您误解了合并语义和容器管理的事务行为。您的 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 调用启动的事务中执行的。

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

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