当前位置: 首页 > 知识库问答 >
问题:

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

巢睿
2023-03-14

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

下面的代码应该回滚DB操作,但它没有回滚(整个类注释为@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.SimpleJarepository,因此它的代码是:

@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规范:

find方法(前提是在没有锁的情况下调用它或使用LockModeType.None调用它)和getReference方法不需要在事务上下文中调用。如果使用具有事务范围持久化上下文的实体管理器,则将分离生成的实体;如果使用具有扩展持久性上下文的实体管理器,则将对它们进行管理。

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

  • 如果X是分离的实体,则将X的状态复制到具有相同标识的预先存在的托管实体实例X'上,或者创建X的新托管副本X'。
  • 如果X是新的实体实例,则创建新的托管实体实例X',并将X的状态复制到新的托管实体实例X'中。
  • 如果X是已删除的实体实例,则合并操作将引发IllegalArgumentExcepthtml" target="_blank">ion(或者事务提交将失败)。
  • 如果X是托管实体,则合并操作将忽略它,但是,如果来自X的关系已用cascade元素值cascade=merge或cascade=ALL注释注释,则合并操作将级联到这些关系引用的实体。
  • 对于由具有级联元素值cascade=merge或cascade=all的X的关系引用的所有实体Y,Y被递归合并为Y'。对于由X引用的所有这样的Y,X'被设置为引用Y'。(请注意,如果管理X,则X是与X'相同的对象。)
  • 如果X是与X'合并的实体,并引用了另一个实体Y,其中未指定CASCADE=MERGE或CASCADE=ALL,则从X'导航相同关联将产生对具有与Y相同持久标识的托管对象Y'的引用。

>

  • 如果merge()返回的副本是托管实体,为什么在使用分离的实体时,更改会存储在DB中?(除非有例外。这是我想要的行为)

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

    根据@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个答案

    潘安平
    2023-03-14

    >

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

    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调用启动的事务中执行的。

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

      null

  •  类似资料:
    • 最近,在一次代码审查中,一位同事说“测试不应该有try-catch块”。他还向我指出了关于的信息,我可以使用它重写对异常数据执行检查的一些测试。 然而,我遇到了一种情况,我不确定我是否能消除试捕。我正在为一个方法编写单元测试,该方法将抛出一个异常,并执行一些我需要使用JMockit验证的行为。假设测试中的代码包含如下内容 JUnit测试当前看起来像 我找不到摆脱试捕的好方法。就我个人而言,我不认为

    • 问题内容: 这是我的问题: 我正在Java EE / Spring / Hibernate应用程序上运行批处理。此批次称为。此方法调用可以抛出的(一个扩展类)。看起来是这样的: 随着执行的继续,会捕获到异常,但是在method1关闭事务时,将引发RollbackException。 这是堆栈跟踪: 当不抛出此异常时,它将很好地工作。 我尝试过的 设置 尝试赶上 但这并没有改变任何东西。 由于异常发

    • 我正在使用Mojang API从Minecraft玩家的用户名返回UUID。此方法在参数(我们想要知道UUID的播放器的用户名)中接受一个字符串。为了使用API的resultat,我使用SimpleJSON库(将JSON结果解析为要返回的字符串)。 我的方法抛出2个检查过的异常:IOExeption和Parse异常,因为我想要。当错误的用户名(因此不存在用户名)时,API返回一个空JSON对象,在

    • 我正在使用JAXB XMLadapter封送和反封送布尔值。应用程序的XML文件也将被C#应用程序访问。我们必须验证这个XML文件,这是使用XSD完成的。C#应用程序为布尔节点编写“true”值。但XSD也验证了这一点,因为它只允许“true/false”或“1/0”。因此,我们在XSD中保留了布尔值的String,该String将由XMLAdapter验证,以便在我们这边进行封送和反封送。XML

    • 问题内容: 这是我的代码块。 这段代码无法编译,因为我在Line1中添加了“ throws”。 编译器抱怨重写的方法不能引发异常。 为什么这样 ?。 为什么覆盖的方法不能引发异常? 因为我可以通过在子类的实现中添加n行代码来覆盖基类中的方法。 这些添加的代码会引发异常,所以为什么我不能在重写的方法中使用“引发”? 问题答案: 重写的方法可以引发Exception,只要被重写的方法也抛出相同的Exc

    • 你可以使用raise语句 引发 异常。你还得指明错误/异常的名称和伴随异常 触发的 异常对象。你可以引发的错误或异常应该分别是一个Error或Exception类的直接或间接导出类。 如何引发异常 例13.2 如何引发异常 #!/usr/bin/python # Filename: raising.py classShortInputException(Exception):     '''A u