我想问一下这种行为的原因,因为在运行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相同的持久性身份。
如果merge(
)返回的副本被认为是被管实体,那么当我使用分离的数据时,为什么更改会存储在数据库中呢? (除非有例外。这是我想要的行为)
如果我修改新的托管实体但抛出异常,为什么还要提交更改?
编辑按照@ 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;
}
}
答案 0 :(得分:0)
您似乎误解了合并语义和容器管理的事务行为。您的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
调用启动的事务中执行的。
从上面写的所有内容中:为证明无回滚行为而提供的两个代码示例实际上是相同的。您可能会遇到一些抱怨的问题:
assignNewFoo
,并在此外部@Transactional方法中执行事务应用程序检查。由于您的传播级别为“REQUIRED”且未在assignNewFoo
调用内捕获RuntimeException,因此一旦assignNewFoo
调用完成,事务将被标记为回滚,但实际回滚将在完成事务的方法后执行已经传播了。