7

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

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

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

编辑:

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

4

3 回答 3

16

我找到了解决方案!

显然 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;
    }
}
于 2013-06-25T17:42:42.180 回答
2

我认为您遇到的行为取决于您使用的 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 这样做了,但您将无法回滚它)

于 2013-06-21T00:11:06.890 回答
1

只是因为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();
    }
}
于 2015-06-02T03:19:17.680 回答