【问题标题】:Java Spring @Transactional method not rolling back as expectedJava Spring @Transactional 方法未按预期回滚
【发布时间】:2013-06-17 23:22:12
【问题描述】:

下面是我正在尝试做的事情的简要概述。我想通过一个方法调用将记录推送到数据库中的两个不同表。如果有什么失败,我希望一切都回滚。因此,如果 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;
    }
}

其中MyDAO dao是一个接口,使用mybatis映射到数据库,使用Spring注入设置。

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

编辑:

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

【问题讨论】:

    标签: java spring transactions


    【解决方案1】:

    我找到了解决办法!

    显然 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;
        }
    }
    

    【讨论】:

    • 另见:stackoverflow.com/questions/4396284/…;如果您使用 AspectJ 而不是 Spring 代理,Spring 可以拦截来自同一类的方法调用(并且 @Transcational 也适用于私有方法)
    【解决方案2】:

    只是因为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();
        }
    }
    

    【讨论】:

      【解决方案3】:

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

      如果您确实使用休眠启用 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
      

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

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

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

      【讨论】:

      • 请发布您的弹簧配置。
      • @gerrytan 我编辑了我的问题以更准确地描述我在做什么。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-04-14
      • 2017-09-06
      • 2020-06-12
      • 2018-11-08
      • 1970-01-01
      • 2015-10-13
      • 2020-03-20
      相关资源
      最近更新 更多