如果 B 出错,请回滚 A。弹簧靴, jdbc模板

我有一个方法,“databaseChanges”,它以迭代方式调用2个操作:A,B。“A”在前,“B”在后。'A' & 'B' 可以是 Create, Update Delete 功能在我的持久存储,Oracle Database 11g。

比方说,

“A”更新表 Users 中的记录,属性 zip,其中 id = 1。

“B”在表格爱好中插入记录。

场景:数据库调用更改方法,“A”操作并更新记录。“B”操作并尝试插入记录,发生某些情况,引发异常,异常冒泡到数据库Changes方法。

预期:“A”和“B”没有改变任何东西。“A”所做的更新将是回滚。“B”什么也没改变,嗯...有一个例外。

实际:“A”更新似乎未回滚。“B”什么也没改变,嗯...有一个例外。


一些代码

如果我有联系,我会做这样的事情:

private void databaseChanges(Connection conn) {
   try {
          conn.setAutoCommit(false);
          A(); //update.
          B(); //insert
          conn.commit();
   } catch (Exception e) { 
        try {
              conn.rollback();
        } catch (Exception ei) {
                    //logs...
        }
   } finally {
          conn.setAutoCommit(true);
   }
}

问题:我没有连接(请参阅随问题一起发布的标签)

我试图:

@Service
public class SomeService implements ISomeService {
    @Autowired
    private NamedParameterJdbcTemplate jdbcTemplate;
    @Autowired
    private NamedParameterJdbcTemplate npjt;

    @Transactional
    private void databaseChanges() throws Exception {   
        A(); //update.
        B(); //insert
    }
}

我的应用配置类:

import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

@Configuration
public class AppConfig {    
    @Autowired
    private DataSource dataSource;

    @Bean
    public NamedParameterJdbcTemplate namedParameterJdbcTemplate() {
        return new NamedParameterJdbcTemplate(dataSource);
    }   
}

“A”进行更新。从“B”中引发异常。由“A”进行的更新未回滚。

从我读到的内容中,我明白我没有正确使用@Transactional。我阅读并尝试了几篇博客文章和stackverflow问答,但没有成功解决我的问题。

有什么建议吗?


编辑

有一个调用数据库Changes()方法的方法

public void changes() throws Exception {
    someLogicBefore();
    databaseChanges();
    someLogicAfter();
}

哪种方法应该用@Transactional注释,

更改()?数据库更改()?


答案 1

@Transactionalspring中的注释的工作原理是将对象包装在代理中,而代理又包装了事务中注释的方法。由于该注释不适用于私有方法(如您的示例中所示),因为私有方法不能被继承=>它们不能被包装(如果您将声明性事务与aspectj一起使用,则情况并非如此,那么下面与代理相关的警告不适用)。@Transactional

以下是关于弹簧魔法如何工作的基本解释。@Transactional

你写道:

class A {
    @Transactional
    public void method() {
    }
}

但这是你注射豆子时实际得到的:

class ProxiedA extends A {
   private final A a;

   public ProxiedA(A a) {
       this.a = a;
   }

   @Override
   public void method() {
       try {
           // open transaction ...
           a.method();
           // commit transaction
       } catch (RuntimeException e) {
           // rollback transaction
       } catch (Exception e) {
           // commit transaction
       }
   }
} 

这是有局限性的。它们不适用于方法,因为它们是在对象代理之前调用的。即使您正确配置了所有事务,默认情况下,事务也只会在未经检查的异常时回滚。如果需要在某些已检查的异常上回滚,请使用此选项。@PostConstruct@Transactional(rollbackFor={CustomCheckedException.class})

我知道的另一个经常遇到的警告:

@Transactional方法只有在您调用它“从外部”时才有效,在下面的示例中不会被包装在事务中:b()

class X {
   public void a() {
      b();
   }

   @Transactional
   public void b() {
   }
}

这也是因为通过代理您的对象来工作。在上面的示例中,不会调用增强的“spring代理”方法,因此不会有事务。作为一种解决方法,您必须从另一个bean调用。@Transactionala()X.b()b()b()

当您遇到任何这些警告并且无法使用建议的解决方法(使方法非私有或从其他Bean调用)时,您可以使用声明性事务代替:b()TransactionTemplate

public class A {
    @Autowired
    TransactionTemplate transactionTemplate;

    public void method() {
        transactionTemplate.execute(status -> {
            A();
            B();
            return null;
        });
    }

...
} 

更新

使用上述信息回答OP更新的问题。

哪种方法应该用@Transactional:changes()进行注释?数据库更改()?

@Transactional(rollbackFor={Exception.class})
public void changes() throws Exception {
    someLogicBefore();
    databaseChanges();
    someLogicAfter();
}

确保称为Bean的“从外部”,而不是从类本身和上下文被实例化之后(例如,这不是或注释方法)。了解默认情况下,弹簧回滚仅针对未选中的异常进行事务(尝试在回滚中更具体地针对已检查的异常列表)。changes()afterPropertiesSet()@PostConstruct


答案 2

任何触发器都会回滚,任何选中的异常都不会回滚。RuntimeException

这是所有 Spring 事务 API 的常见行为。默认情况下,如果从事务代码中引发 a,则事务将回滚。如果抛出已检查的异常(即不是 ),则不会回滚事务。RuntimeExceptionRuntimeException

这取决于您在函数内部获得的异常。因此,为了捕获所有异常,您需要做的就是添加databaseChangesrollbackFor = Exception.class

更改应该在服务类上,代码将如下所示:

@Service
public class SomeService implements ISomeService {
    @Autowired
    private NamedParameterJdbcTemplate jdbcTemplate;
    @Autowired
    private NamedParameterJdbcTemplate npjt;

    @Transactional(rollbackFor = Exception.class)
    private void databaseChanges() throws Exception {   
        A(); //update
        B(); //insert
    }
}

此外,你可以用它做一些好事,所以不是所有的时间你都必须写。您可以通过编写自己的自定义注释来实现此目的:rollbackFor = Exception.class

@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(rollbackFor = Exception.class)
@Documented
public @interface CustomTransactional {  
}

最终代码将如下所示:

@Service
public class SomeService implements ISomeService {
    @Autowired
    private NamedParameterJdbcTemplate jdbcTemplate;
    @Autowired
    private NamedParameterJdbcTemplate npjt;

    @CustomTransactional
    private void databaseChanges() throws Exception {   
        A(); //update
        B(); //insert
    }
}

推荐