【问题标题】:UnexpectedRollbackException in a @Transactional class when method is marked with noRollbackFor a thrown exception当方法被标记为 noRollbackFor 时,@Transactional 类中的 UnexpectedRollbackException 抛出异常
【发布时间】:2020-07-29 17:54:02
【问题描述】:

我有一个 @Transactional 标记类 FooServiceImpl,它需要使用从另一个 @Transactional 类 BarServiceImpl 获得的值填充对象 FooDetails。

当尝试从 BarServiceImpl 获取值时,会抛出 Exception1。我想将设置器抛出异常的 FooDetails 字段留空并捕获异常。所有其他字段必须 即使某些 setter 抛出异常,也会保持填充状态。

遗憾的是,事实并非如此,因为尽管我将方法明确标记为“noRollbackFor = Exception1.class”并捕获了异常,但由于抛出了 Exception1,事务已被标记为仅回滚。

为什么会发生这种情况,我如何修复代码以填充设置器未抛出异常的所有字段,并将设置器抛出异常的字段留空。 以下是导致该行为的代码:

@Service
@Transactional
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TransactionalService {
    // The name of the service
    String value() default "";
}

public class FooDetails {
    private String fieldA;
    private String fieldB;

    // getters and setters
    ...
}

@TransactionalService 
public class BarServiceImpl implements BarService {
    @Override
    public String getFieldB() throws Exception1 {
        ...
        try {
            ...
        } catch (Exception2 e2) {
            ...
            throw new Exception1();
        } catch (Exception3 e3) {
            ...
            throw new Exception1();
        }
    }
}

@TransactionalService 
public class FooServiceImpl implements FooService {
    ...
    ...

    @Autowired BarService barService;

    @Override
    @Transactional(noRollbackFor = {Exception1.class, Exception2.class, Exception3.class})
    public FooDetails getFooDetails(Long fooId) {
        Foo foo = fooDao.get(fooId);
        if (foo == null) {
            return null;
        }

        FooDetails fooDetails = new FooDetails();

        // getAFooDetails() has no exceptions to throw => ok
        fooDetails.setFieldA(getAFooDetails());

        // barService.getFieldB() throws an Exception1 => results in 
        // "org.springframework.transaction.UnexpectedRollbackException:
        //           Transaction rolled back because it has been marked as rollback-only" => all foo fields left empty
        try {
            fooDetails.setFieldB(barService.getFieldB());
        } catch (Exception1 e) {
            LOG.info(e.getMessage());
        }        
    }
}

【问题讨论】:

  • 你能显示异常类吗?
  • @InsertKnowledge 没有什么特别的。只是带有扩展 Exception 的自定义错误消息的自定义异常。

标签: java spring spring-boot exception transactions


【解决方案1】:

您正在使用两个逻辑事务范围:外部事务范围(在 FooServiceImpl 中)和内部事务范围(在 BarServiceImpl 中)。两者都属于同一个物理事务。

既然你说 Exception1 是一个检查异常,抛出它永远不会导致 BarServiceImpl 中的任何回滚(直到显式声明)。 在 FooServiceImpl 中捕获它也不会导致任何回滚。

通常,如果内部事务范围(此处:您的 BarServiceImpl.getFieldB)触发回滚,则会引发 UnexpectedRollbackException。 当外部事务范围的调用方法(这里:您的 FooServiceImpl.getFooDetails)正常返回时,Spring 的 AbstractPlatformTransactionManager 将尝试提交。此提交失败,因为其他人已将其标记为“rollbackOnly”(由内部事务范围完成)。然后它会抛出一个 UnexpectedRollbackException 给 FooServiceImpl.getFooDetails 的调用者。

所以,您的 BarServiceImpl.getFieldB 一定以某种方式抛出了未经检查的异常(不同于 Exception1)

或

您在此处未显示的代码中的其他地方使用了单个物理事务,并且这段代码确实通过未经检查的异常触发了回滚。

【讨论】:

    猜你喜欢
    • 2016-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-12
    • 2012-08-10
    • 1970-01-01
    • 2012-09-20
    相关资源
    最近更新 更多