@Transactional方法抛出异常

时间:2016-04-14 07:31:39

标签: spring hibernate jpa transactional

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

我有以下代码,它应该回滚数据库操作,但它没有(整个类注释为@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()方法来自类org.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()。根据{{​​3}}:

  

find方法(前提是它在没有锁定的情况下调用或使用   LockModeType.NONE)和getReference方法不是必需的   在事务上下文中调用。 如果是实体经理   事务范围的持久化上下文正在使用中,由此产生   实体将被分离;如果实体经理有扩展   使用持久化上下文,它们将被管理。

  

合并操作允许从分离的状态传播状态   实体到实体管理器管理的持久实体。该   应用于实体X的合并操作的语义为   如下:

     
      
  • 如果X是分离的实体,则将X的状态复制到预先存在的管理实体实例X&#39;相同的身份或新的   管理副本X&#39;创建了X.
  •   
  • 如果X是新的实体实例,则新的管理实体实例X&#39;已创建,并且X的状态将复制到新的管理实体中   实例X&#39;。
  •   
  • 如果X是已删除的实体实例,则合并操作将抛出IllegalArgumentException(或者事务提交将   失败)。
  •   
  • 如果X是托管实体,则合并操作会忽略它,但是,合并操作会级联到由   如果这些关系已被注释,则来自X的关系   cascade element value cascade = MERGE或cascade = ALL annotation。
  •   
  • 对于具有级联元素值cascade = MERGE或cascade = ALL的X关系引用的所有实体Y,Y被合并   递归地为Y&#39;。对于由X,X&#39;引用的所有这样的Y.被设置为   参考Y&#39;。 (注意,如果X被管理,那么X是与之相同的对象   X&#39;。)
  •   
  • 如果X是合并到X&#39;的实体,引用另一个实体Y,其中未指定cascade = MERGE或cascade = ALL,则   从X&#39;导航相同的关联产生对a的引用   管理对象Y&#39;具有与Y相同的持久性身份。
  •   
  1. 如果merge()返回的副本被认为是被管实体,那么当我使用分离的数据时,为什么更改会存储在数据库中呢? (除非有例外。这是我想要的行为)

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

  3. 编辑按照@ 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 个答案:

答案 0 :(得分:0)

  1. 您似乎误解了合并语义和容器管理的事务行为。您的assignNewFoo方法是事务性的,并且您的“bean”实例是从存储库加载的。因此,'bean'实例一直被管理,直到事务结束(或直到您手动从持久性上下文中删除)。这意味着myBeanRepository.save(bean);调用不执行任何操作,因为'bean'已经是JPA管理的实体。只要在同一事务中执行了“findOne”已发布,myBeanRepository.save(bean) == bean将为真。合并用于将对实体的非托管实例所做的更改应用于托管实例。此代码说明了案例合并用于:

    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 Spec条目,您可以参考:它在这里不适用。它说的是非事务性搜索,但您的搜索是在assignNewFoo调用启动的事务中执行的。

  2. 从上面写的所有内容中:为证明无回滚行为而提供的两个代码示例实际上是相同的。您可能会遇到一些抱怨的问题:

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