如何防止JPA回滚事务?

9

调用的方法:
1. Struts Action
2. 服务类方法(使用@Transactional注解)
3. Xfire webservice调用

所有内容,包括struts(DelegatingActionProxy)和事务都是通过Spring配置的。

持久性是使用JPA/Hibernate完成的。

有时Web服务会抛出未经检查的异常。我捕获此异常并抛出已检查的异常。我不希望事务回滚,因为Web服务异常会改变当前状态。我像这样注释了该方法:

@Transactional(noRollbackFor={XFireRuntimeException.class, Exception.class})
public ActionForward callWS(Order order, ....) throws Exception
  (...)
  OrderResult orderResult = null;

  try {
    orderResult = webService.order(product, user)
  } catch (XFireRuntimeException xfireRuntimeException) {
    order.setFailed(true);
    throw new WebServiceOrderFailed(order);
  } finally {
    persist(order);
  }
}

我仍然会得到这个异常:
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly

当我尝试使用junit重现此问题时,事务不会被标记为回滚,仍然可以提交该事务。

我如何让Spring不回滚该事务?

2个回答

9

我已经成功为这个问题创建了一个测试案例:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"file:web/WEB-INF/spring/applicationContext.xml",
        "file:web/WEB-INF/spring/services.xml"})
@Transactional
public class DoNotRollBackTest {
    @Autowired FakeService fakeService;

    @Test
    @Rollback(false)
    public void testRunXFireException() {
        fakeService.doSomeTransactionalStuff();
    }
}

FakeService:
@Service
public class FakeService {
    @Autowired private EcomService ecomService;
    @Autowired private WebService webService;

    @Transactional(noRollbackFor={XFireRuntimeException.class})
    public void doSomeTransactionalStuff() {
        Order order = ecomService.findOrderById(459);

        try {
            webService.letsThrowAnException();
        } catch (XFireRuntimeException e) {
            System.err.println("Caugh XFireRuntimeException:" + e.getMessage());
        }

        order.setBookingType(BookingType.CAR_BOOKING);
        ecomService.persist(order);
    }
}

WebService:

@Transactional(readOnly = true)
public class WebService {
    public void letsThrowAnException() {
        throw new XFireRuntimeException("test!");
    }
}

这将重新创建回滚异常。

后来我意识到在WebService.letsThrowAnException中,事务可能被标记为rollbackOnly,因为WebService也是事务性的。我转移到了注释:

@Transactional(noRollbackFor={XFireRuntimeException.class})
    public void letsThrowAnException() {

现在事务没有被回滚,我可以提交对订单的更改。

3

您不能在Spring可以看到的地方抛出异常。在这种情况下,您不能抛出WebServiceOrderFailed()异常。解决方法是将代码拆分为两个方法。第一个方法处理错误并返回异常,外部方法创建事务。

[编辑]关于noRollbackFor:尝试用WebServiceOrderFailed.class替换Exception.class


1
这是不正确的。noRollbackFor 检查指定的异常类及其所有子类:http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/transaction/annotation/Transactional.html#noRollbackFor()此外,默认情况下,已检查异常不会触发回滚:http://static.springsource.org/spring/docs/2.5.x/reference/transaction.html#transaction-declarative-attransactional-settings - ChssPly76
1
这并没有解释为什么上面的代码会在 WebServiceOrderFailed 上回滚。 - Aaron Digulla
我的猜测是WebServiceOrderFailed是一个RuntimeException,而且上面的代码(noRollbackFor={..., Exception.class})不会有任何影响,因为Exception被特殊处理了(否则,继承代码也会忽略RuntimeException,因为它扩展了Exception)。 - Aaron Digulla
感谢您的输入。你们指引了我正确的方向!(WebServiceOrderFailed扩展自Exception,它是一个已检查的异常) - D. Wroblewski
请帮忙,我面临着相同的问题 https://stackoverflow.com/questions/54343137/problem-in-handling-unit-of-work-using-hibernate-jpa - Alagammal P

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接