如果B出错,请回退A. spring boot,jdbctemplate

我有一个方法’databaseChanges’,它以迭代的方式调用2个操作:A,B。 ‘A’首先,’B’最后。 ‘A’和’B’可以是我的持久存储,Oracle Database 11g中的U pdate D eletefunction。

比方说,

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

‘B’在表爱好中插入记录。

场景:调用了databaseChanges方法,’A’操作并更新记录。 ‘B’操作并尝试插入记录,发生某种情况,抛出exception,exception是冒充dbChanges方法。

预期: ‘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 } } 

我的AppConfig类:

 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问答,但没有成功解决我的问题。

有什么建议么?


编辑

有一种方法可以调用databaseChanges()方法

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

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

变化()? databaseChanges()?

Spring中的@Transactional注释通过将对象包装在代理中来工作,代理又在事务中包装使用@Transactional注释的方法。 因为该注释不适用于私有方法(如在您的示例中),因为私有方法不能被inheritance =>它们不能被包装(如果您使用与aspectj的声明式事务,则不是这样,那么与代理相关的警告以下不适用)。

以下是@Transactional spring magic如何工作的基本解释。

你写了:

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

但这是你注入bean时实际获得的:

 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方法,因为它们在代理对象之前被调用。 即使您正确配置了所有内容,默认情况下,事务仅在未经检查的exception上回滚。 如果需要对某些已检查的exception进行回滚,请使用@Transactional(rollbackFor={CustomCheckedException.class})

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

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

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

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

当您遇到任何这些警告并且无法使用建议的解决方法(make方法非私有或从另一个bean调用b() )时,您可以使用TransactionTemplate而不是声明性事务:

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

更新

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

应该用@Transactional注释哪个方法:changes()? databaseChanges()?

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

确保从bean的“外部”调用changes() ,而不是从类本身调用,并且在实例化上下文之后(例如,这不是afterPropertiesSet()@PostConstruct注释方法)。 默认情况下,请仅了解未经检查的exception的spring rollbacks事务(尝试在rollbackFor checked exception列表中更具体)。

任何RuntimeException触发回滚,而任何已检查的Exception都不会。

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

这取决于您在databaseChanges函数中获得的exception。 因此,为了捕获所有exception,您需要做的就是添加rollbackFor = 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 } } 

您提供的第一个代码是UserTransactions,即应用程序必须执行事务管理。 通常您希望容器处理它并使用@Transactional注释。 我认为你的问题可能是,你在私有方法上有注释。 我将注释移到类级别

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

然后它应该正确回滚。 您可以在此处找到更多详细信息Spring @Transactional属性是否适用于私有方法?

尝试这个:

 @TransactionManagement(TransactionManagementType.BEAN) public class MyFacade { @TransactionAttribute(TransactionAttribute.REQUIRES_NEW) public void databaseChanges() throws Exception { A(); //update. B(); //insert } 

您似乎缺少的是TransactionManagerTransactionManager的目的是能够管理数据库事务。 有两种类型的交易,程序化和声明式。 您所描述的是需要通过注释进行声明性事务。

那么您需要为项目准备的是以下内容:

Spring Transactions依赖(以Gradle为例)

 compile("org.springframework:spring-tx") 

在Spring Boot配置中定义事务管理器

像这样的东西

 @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } 

您还需要添加@EnableTransactionManagement注释(不确定这是否是在较新版本的spring boot中免费提供的。

 @EnableTransactionManagement public class AppConfig { ... } 

添加@Transactional

在这里,您将为要参与事务的方法添加@Transactional注释

 @Transactional public void book(String... persons) { for (String person : persons) { log.info("Booking " + person + " in a seat..."); jdbcTemplate.update("insert into BOOKINGS(FIRST_NAME) values (?)", person); } }; 

请注意,此方法应该是公开的而不是私有的。 您可能需要考虑将@Transactional放在调用databaseChanges()的公共方法上。

还有关于@Transactional应该去哪里以及它的行为方式的高级主题,所以最好先做一些工作,然后稍后探索这个区域:)

在完成所有这些(依赖+事务管理器配置+注释)后,事务应该相应地工作。

参考

关于事务的Spring参考文档

使用Spring Boot的Spring交易指南 – 这里有你可以使用的示例代码

你需要的是这样的:

 @Transactional(propagation=Propagation.REQUIRES_NEW, rollbackFor = {Exception.class}) public void databaseChanges() throws Exception { A(); //update. B(); //insert } @Transactional(propagation=Propagation.REQUIRED, rollbackFor = {Exception.class}) public void A() throws Exception { // update } @Transactional(propagation=Propagation.REQUIRED, rollbackFor = {Exception.class}) public void B() throws Exception { // insert } 

将传播指定为REQUIRES_NEW可确保为databaseChanges()方法启动新事务,并且A()B()参与同一事务,其传播指定为REQUIRED

您需要确保使用@Transactional注释注释的方法是公共的,因为事务建议仅适用于公共方法。 注释为此类的私有方法不会抛出错误,但不会表现出事务行为。

现在,当插入时B()发生exception时,事务管理器在内部检查回滚规则(由rollbackFor指定); 它找到Exception.class并将事务(在databaseChanges()上启动)标记为仅回滚,并将随之回滚A(),因为A()正在参与同一事务

如果这不能解决您的问题,请启用springframework跟踪日志并为我提供。 启用后,事务边界和事件将精确记录在这些日志中。

如果您没有看到正确的事务日志记录,请检查您的应用程序是否实际启用了事务管理。 在配置类上添加@EnableTransactionManagement可启用它。

链接:
传播
的rollbackFor
私有方法的交易
@EnableTransactionManagement