内部事务(REQUIRES_NEW)抛出异常时外部事务回滚
External transaction rollback when internal transaction(REQUIRES_NEW) throw exception
我有方法:
@Transactional
public void importChargesRequest() {
...
for (Charge charge : charges) {
try {
Charge savedCharge = saveCharge(charge);
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
为了坚持每个 Charge
我调用内部方法:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Charge saveCharge(Charge charge) {
return chargesRepository.saveAndFlush(charge);
}
如果saveCharge
方法抛出异常(在我的例子中是约束异常)我想写日志,并继续保留另一个实体。但是当我捕获异常时 - 我的外部事务回滚错误:
Transaction was marked for rollback only; cannot commit; nested exception is org.hibernate.TransactionException: Transaction was marked for rollback only; cannot commit
我需要打开事务并开始保存每个实体。如果某些实体无法保存异常 - 我需要记录此异常并继续保存其他实体。当所有实体都将保存(或记录)时,我需要提交外部事务。但是现在它回滚了。我该如何解决?
编辑:
我接受了评论并将 REQUIRES_NEW 事务移动到另一个 bean:
@Service
public class TestService {
private final TestDao testDao;
public TestService(TestDao testDao) {
this.testDao = testDao;
}
@Transactional
public void saveTest() {
for (int i = 0; i < 100; i++) {
Test test = new Test();
if (i == 10 || i == 20) {
test.setName("123");
} else {
test.setName(UUID.randomUUID().toString());
}
testDao.save(test);
}
}
}
和每个内部事务的另一个 bean:
@Slf4j
@Component
@Repository
public class TestDao {
@PersistenceContext
private EntityManager entityManager;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void save(Test test) {
entityManager.persist(test);
}
}
当我第一次尝试保存时,数据库中有 20 行。每次下一次保存我都会得到 +10 行。名字有约束。当我收到错误时 - 事务已提交且未继续。每次保存后我等待 98 行。
您应该将方法 saveCharge
移至其他方法 class
因为 spring 交易使用代理模式,这需要其他 class.
创建新服务 class 并注入主要 class
例如:
@Service
public class A{
@Autowire
private B b;
@Transactional
public void importChargesRequest() {
...
for (Charge charge : charges) {
try {
Charge savedCharge = b.saveCharge(charge);
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
}
@Service
public class B{
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Charge saveCharge(Charge charge) {
return chargesRepository.saveAndFlush(charge);
}
}
如果 saveCharge
是与 importChargesRequest
相同的 bean 中的方法,则 @Transactional
注释将被忽略,并且 saveAndFlush 在同一(外部)事务中工作。
(我确定当您使用 proxies/interceptors 管理事务时就是这种情况。我相当确定使用基于 aspectj 的事务拦截时也是如此)。
通常情况下,如果异常冒泡到外部方法(标记为@Transaction 的方法),事务只会被标记为回滚,但我怀疑存储库或事务管理器本身(休眠会话)直接标记由于违反约束而回滚的事务。
解决方案是将 saveCharge 移动到另一个 bean 并使用 importChargesRequest 方法将该 bean 注入到该 bean 中。
@Service
public class ChargesDataService{
@Autowire
private ChargesRepository chargesRepository;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Charge saveCharge(Charge charge) {
return chargesRepository.saveAndFlush(charge);
}
}
@RestController
public class ChargesController{
@Autowire
private ChargesDataService chargesDataService;
@Transactional
public void importChargesRequest() {
for (Charge charge : charges) {
try {
Charge savedCharge = chargesDataService.saveCharge(charge);
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
}
附录:
注释被忽略,因为您不再通过代理到 bean 实例,这意味着没有拦截器被调用,这意味着没有地方可以处理新事务。
您可以通过在 saveCharge
方法中设置断点并查看堆栈跟踪^来检查是否属于这种情况。寻找类似事务拦截器 invokeWithinTransaction 之类的方法。
^ 您还可以创建一个新异常,调用 fillInStacktrace,然后 log/print 包含其堆栈跟踪的异常。
您正在执行 saveCharge(charge)
,即 this.saveCharge(charge)
(您正在以这种方式绕过 spring 代理)- spring 无法创建 new 对此的交易 - 您需要在单独的 Bean
中使用该方法
我有方法:
@Transactional
public void importChargesRequest() {
...
for (Charge charge : charges) {
try {
Charge savedCharge = saveCharge(charge);
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
为了坚持每个 Charge
我调用内部方法:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Charge saveCharge(Charge charge) {
return chargesRepository.saveAndFlush(charge);
}
如果saveCharge
方法抛出异常(在我的例子中是约束异常)我想写日志,并继续保留另一个实体。但是当我捕获异常时 - 我的外部事务回滚错误:
Transaction was marked for rollback only; cannot commit; nested exception is org.hibernate.TransactionException: Transaction was marked for rollback only; cannot commit
我需要打开事务并开始保存每个实体。如果某些实体无法保存异常 - 我需要记录此异常并继续保存其他实体。当所有实体都将保存(或记录)时,我需要提交外部事务。但是现在它回滚了。我该如何解决?
编辑:
我接受了评论并将 REQUIRES_NEW 事务移动到另一个 bean:
@Service
public class TestService {
private final TestDao testDao;
public TestService(TestDao testDao) {
this.testDao = testDao;
}
@Transactional
public void saveTest() {
for (int i = 0; i < 100; i++) {
Test test = new Test();
if (i == 10 || i == 20) {
test.setName("123");
} else {
test.setName(UUID.randomUUID().toString());
}
testDao.save(test);
}
}
}
和每个内部事务的另一个 bean:
@Slf4j
@Component
@Repository
public class TestDao {
@PersistenceContext
private EntityManager entityManager;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void save(Test test) {
entityManager.persist(test);
}
}
当我第一次尝试保存时,数据库中有 20 行。每次下一次保存我都会得到 +10 行。名字有约束。当我收到错误时 - 事务已提交且未继续。每次保存后我等待 98 行。
您应该将方法 saveCharge
移至其他方法 class
因为 spring 交易使用代理模式,这需要其他 class.
创建新服务 class 并注入主要 class
例如:
@Service
public class A{
@Autowire
private B b;
@Transactional
public void importChargesRequest() {
...
for (Charge charge : charges) {
try {
Charge savedCharge = b.saveCharge(charge);
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
}
@Service
public class B{
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Charge saveCharge(Charge charge) {
return chargesRepository.saveAndFlush(charge);
}
}
如果 saveCharge
是与 importChargesRequest
相同的 bean 中的方法,则 @Transactional
注释将被忽略,并且 saveAndFlush 在同一(外部)事务中工作。
(我确定当您使用 proxies/interceptors 管理事务时就是这种情况。我相当确定使用基于 aspectj 的事务拦截时也是如此)。
通常情况下,如果异常冒泡到外部方法(标记为@Transaction 的方法),事务只会被标记为回滚,但我怀疑存储库或事务管理器本身(休眠会话)直接标记由于违反约束而回滚的事务。
解决方案是将 saveCharge 移动到另一个 bean 并使用 importChargesRequest 方法将该 bean 注入到该 bean 中。
@Service
public class ChargesDataService{
@Autowire
private ChargesRepository chargesRepository;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Charge saveCharge(Charge charge) {
return chargesRepository.saveAndFlush(charge);
}
}
@RestController
public class ChargesController{
@Autowire
private ChargesDataService chargesDataService;
@Transactional
public void importChargesRequest() {
for (Charge charge : charges) {
try {
Charge savedCharge = chargesDataService.saveCharge(charge);
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
}
附录:
注释被忽略,因为您不再通过代理到 bean 实例,这意味着没有拦截器被调用,这意味着没有地方可以处理新事务。
您可以通过在 saveCharge
方法中设置断点并查看堆栈跟踪^来检查是否属于这种情况。寻找类似事务拦截器 invokeWithinTransaction 之类的方法。
^ 您还可以创建一个新异常,调用 fillInStacktrace,然后 log/print 包含其堆栈跟踪的异常。
您正在执行 saveCharge(charge)
,即 this.saveCharge(charge)
(您正在以这种方式绕过 spring 代理)- spring 无法创建 new 对此的交易 - 您需要在单独的 Bean