Java Spring @Transactional 方法未按预期回滚

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17224887/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-01 01:22:00  来源:igfitidea点击:

Java Spring @Transactional method not rolling back as expected

javaspringtransactions

提问by Franklin

Below is a quick outline of what I'm trying to do. I want to push a record to two different tables in the database from one method call. If anything fails, I want everything to roll back. So if insertIntoBfails, I want anything that would be committed in insertIntoAto be rolled back.

下面是我正在尝试做的事情的快速概述。我想通过一个方法调用将一条记录推送到数据库中的两个不同表。如果有任何失败,我希望一切都回滚。因此,如果insertIntoB失败,我希望将提交的任何内容insertIntoA回滚。

public class Service {
    MyDAO dao;

    public void insertRecords(List<Record> records){
        for (Record record : records){
            insertIntoAAndB(record);
        }
    }

    @Transactional (rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public void insertIntoAAndB(Record record){
        insertIntoA(record);
        insertIntoB(record);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void insertIntoA(Record record){
        dao.insertIntoA(record);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void insertIntoB(Record record){
        dao.insertIntoB(record);
    }

    public void setMyDAO(final MyDAO dao) {
        this.dao = dao;
    }
}

Where MyDAO daois an interface that is mapped to the database using mybatis and is set using Spring injections.

哪里MyDAO dao是使用mybatis映射到数据库,使用Spring注入设置的接口。

Right now if insertIntoBfails, everything from insertIntoAstill gets pushed to the database. How can I correct this behavior?

现在,如果insertIntoB失败,所有内容insertIntoA仍然会被推送到数据库。我该如何纠正这种行为?

EDIT:

编辑:

I modified the class to give a more accurate description of what I'm trying to achieve. If I run insertIntoAAndBdirectly, the roll back works if there are any issues, but if I call insertIntoAAndBfrom insertRecords, the roll back doesn't work if any issues arise.

我修改了课程以更准确地描述我想要实现的目标。如果我insertIntoAAndB直接运行,则在出现任何问题时回滚工作,但如果我insertIntoAAndB从调用insertRecords,则在出现任何问题时回滚将不起作用。

回答by Franklin

I found the solution!

我找到了解决方案!

Apparently Spring can't intercept internal method calls to transactional methods. So I took out the method calling the transactional method, and put it into a separate class, and the rollback works just fine. Below is a rough example of the fix.

显然 Spring 无法拦截对事务方法的内部方法调用。于是把调用事务方法的方法取出来,放到一个单独的类中,回滚就好了。以下是修复的粗略示例。

public class Foo {
    public void insertRecords(List<Record> records){
        Service myService = new Service();
        for (Record record : records){
            myService.insertIntoAAndB(record);
        }
    }
}

public class Service {
    MyDAO dao;

    @Transactional (rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public void insertIntoAAndB(Record record){
        insertIntoA(record);
        insertIntoB(record);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void insertIntoA(Record record){
        dao.insertIntoA(record);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void insertIntoB(Record record){
        dao.insertIntoB(record);
    }

    public void setMyDAO(final MyDAO dao) {
        this.dao = dao;
    }
}

回答by gerrytan

I think the behavior you encounter is dependent on what ORM / persistence provider and database you're using. I tested your case using hibernate & mysql and all my transactions rolled back alright.

我认为您遇到的行为取决于您使用的 ORM/持久性提供程序和数据库。我使用 hibernate & mysql 测试了你的情况,我所有的事务都回滚了。

If you do use hibernate enable SQL and transaction logging to see what it's doing:

如果您确实使用休眠,请启用 SQL 和事务日志以查看它在做什么:

log4j.logger.org.hibernate.SQL=DEBUG
log4j.logger.org.hibernate.transaction=DEBUG
// for hibernate 4.2.2 
// log4j.logger.org.hibernate.engine.transaction=DEBUG

If you're on plain jdbc (using spring JdbcTemplate), you can also debug SQL & transaction on Spring level

如果您使用的是普通 jdbc(使用 spring JdbcTemplate),您还可以在 Spring 级别调试 SQL 和事务

log4j.logger.org.springframework.jdbc.core=DEBUG
log4j.logger.org.springframework.transaction=DEBUG

Double check your autocommit settings and database specific peciular (eg: most DDL will be comitted right away, you won't be able to roll it back although spring/hibernate did so)

仔细检查您的自动提交设置和数据库特定的特性(例如:大多数 DDL 将立即提交,尽管 spring/hibernate 这样做了,但您将无法回滚它)

回答by KnightYang

Just because jdk parses aop annotation not only with the method, also parse annotation with the target class. For example, you have method A with @transactional, and method B which calls method A but without @transactional, When you invoke the method B with reflection, Spring AOP will check the B method with the target class has any annotations. So if your calling method in this class is not with the @transactional, it will not parse any other method in this method. At last, show you the source code: org.springframework.aop.framework.jdkDynamicAopProxy.class

正因为jdk不仅用方法解析aop注解,还用目标类解析注解。例如,你有一个带有@transactional的方法A,和调用方法A但没有@transactional的方法B,当你用反射调用方法B时,Spring AOP会检查目标类的B方法是否有任何注释。所以如果你在这个类中的调用方法不带@transactional,它就不会解析这个方法中的任何其他方法。最后给大家看源码:org.springframework.aop.framework.jdkDynamicAopProxy.class

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    ......
    // Get the interception chain for this method.
    List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

    // Check whether we have any advice. If we don't, we can fallback on direct
    // reflective invocation of the target, and avoid creating a MethodInvocation.
    if (chain.isEmpty()) {
    // We can skip creating a MethodInvocation: just invoke the target directly
    // Note that the final invoker must be an InvokerInterceptor so we know it does
    // nothing but a reflective operation on the target, and no hot swapping orfancy proxying.
        retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
    }
    else {
    // We need to create a method invocation...
        invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
    // Proceed to the joinpoint through the interceptor chain.
    retVal = invocation.proceed();
    }
}