Spring AOP 在事务回滚后带来异常

Spring AOP bring exception after transaction rollback

我只是玩代码,并尝试了解 spring 中的事务和回滚是如何工作的(我正在使用 spring-boot 和 spring-dao)。然而,在我过期期间,我遇到了我无法解释的异常情况。 所以我有一个控制器

@RestController
public class OrderController {

@Autowired
CarSvc carSvc;

@Autowired
OrderService orderSvc;

@RequestMapping(value="/administrator/order/{id}", produces = "application/json;charset=UTF-8", method = RequestMethod.PUT)
@ResponseStatus(HttpStatus.OK)
public Message closeOrder(@PathVariable Long id){
    orderSvc.closeOrder(id);
    return new Massage("test");
}

}

此控制器调用此服务:

@Service
@Transactional(propagation = Propagation.REQUIRED)
public class OrderSvc extends AbstractService implements OrderService {

public void closeOrder(Long id) {
    final Order order = getDAO().findOne(id);
    Boolean isUserCanCloseOrder = order.getCarWashId().equals(getCarWashIdForAdmin());
    if(isUserCanCloseOrder){
        final Iterable<Order> all = getDAO().findAll();
        for(Order o : all){
            try {
                orderRepo.closeOrder(o);
            }catch (Exception e){
                System.out.printf("error id=" + o.getId() + " , message : " + e.getMessage());
            }

        }

    }else {
        throw new AuthorizationException("User is not allowed to close order with id = " + id);
    }
}

}

并且 try 块中的此服务调用以下存储库

@Repository
public class OrderRepo implements OrderRepository {

@Override
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void closeOrder(Order order) {
    if(order.getClose() != null){
        order.setUpdateVersion(77);
    }else{
        order.setCarWashId(1000L + order.getId());
    }
    final List<OrderedService> services = order.getServices();
    for(OrderedService s: services){
        s.setUpdateVersion(77);
        orderedServiceDAO.save(s);
    }
    orderDAO.save(order);
}

}

逻辑如下:对于存储库中 order 的一部分,我只设置了更新版本(我需要了解数据库是否已更新),其余部分 setCarWashId 没有有效值调用 sql 异常并查看回滚是否正常工作(sql 由于外键约束失败导致的异常)。

一开始,应用程序运行良好,遍历所有订单,并向我显示 System.out.printf 来自服务的所有错误订单。但是当循环结束时,应用程序失败并出现以下异常:

com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`pitstop`.`orders`, CONSTRAINT `order_FK1` FOREIGN KEY (`car_wash_id`) REFERENCES `carwash` (`id`))
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_121]
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[na:1.8.0_121]
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.8.0_121]
at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[na:1.8.0_121]
at com.mysql.jdbc.Util.handleNewInstance(Util.java:404) ~[mysql-connector-java-5.1.38.jar:5.1.38]
at com.mysql.jdbc.Util.getInstance(Util.java:387) ~[mysql-connector-java-5.1.38.jar:5.1.38]
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:932) ~[mysql-connector-java-5.1.38.jar:5.1.38]
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3878) ~[mysql-connector-java-5.1.38.jar:5.1.38]
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3814) ~[mysql-connector-java-5.1.38.jar:5.1.38]
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2478) ~[mysql-connector-java-5.1.38.jar:5.1.38]
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2625) ~[mysql-connector-java-5.1.38.jar:5.1.38]
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2551) ~[mysql-connector-java-5.1.38.jar:5.1.38]
at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1861) ~[mysql-connector-java-5.1.38.jar:5.1.38]
at com.mysql.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2073) ~[mysql-connector-java-5.1.38.jar:5.1.38]
at com.mysql.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2009) ~[mysql-connector-java-5.1.38.jar:5.1.38]
at com.mysql.jdbc.PreparedStatement.executeLargeUpdate(PreparedStatement.java:5094) ~[mysql-connector-java-5.1.38.jar:5.1.38]
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1994) ~[mysql-connector-java-5.1.38.jar:5.1.38]
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:208) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:62) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3281) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:3183) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3525) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:159) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:465) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:351) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:350) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:56) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1258) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:425) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:177) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:77) ~[hibernate-entitymanager-4.3.11.Final.jar:4.3.11.Final]
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:517) ~[spring-orm-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:761) ~[spring-tx-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730) ~[spring-tx-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:485) ~[spring-tx-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:291) ~[spring-tx-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) ~[spring-tx-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208) ~[spring-aop-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at com.sun.proxy.$Proxy128.closeOrder(Unknown Source) ~[na:na]
at biz.controllers.rest.administrator.OrderController.closeOrder(OrderController.java:52) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_121]

我理解由于 FK 错误导致的异常,但为什么会出现?我有异常捕获块。为什么在日志中只提到了我的控制器 (biz.controllers.rest.administrator.OrderController.closeOrder) 而没有 link 服务或存储库?

请解释一下,这是怎么回事。 谢谢。

I understand the exception due to incorrect FK but why it appear? I have catch block for exception.

只要处理链任何地方出现异常,spring的事务管理器"marks the transaction for rollback".即使您在处理链的外部调用层有一个 catch 块(就像您在控制器中有它一样),请求也会失败并且整个事务将被回滚。通过在 OrderRepo 的 @Transaction 定义中指定 rollbackFor = Exception.class,您要求 Spring 在发生任何异常时回滚 整个事务 。讨论了一些类似的问题 here on spring forum

此外,根据您的堆栈跟踪,如果您查看 org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:761) 中第 761 行附近的 Spring 事务处理代码,您将能够了解内部发生的情况。在那里放置一个断点并了解 Spring 正在处理事务和 throwing/handling/logging 异常的条件。这就是我经常理解 Spring 事务管理试图做的事情的方式。

And why in log was mentioned only my controller (biz.controllers.rest.administrator.OrderController.closeOrder) and there is not link to services or repository?

at com.sun.proxy.$Proxy128.closeOrder(Unknown Source) ~[na:na]

堆栈跟踪中的这一行确实表示对 OrderService 的调用(通过代理)。通常,您应该在代理调用程序解析并调用实际 class 的某个点进一步看到堆栈跟踪中的实际 class。你确定你在这里提供了完整的堆栈跟踪吗?你删除了一些行吗?日志中是否还有其他堆栈跟踪或只有这一个?

我不知道原因,但由于 @Transactional(propagation = Propagation.REQUIRED) 在 class OrderSvc 中出现了异常。删除此注释后,问题消失了