如果 B 出错,则回滚 A。 spring 引导,jdbctemplate

Roll back A if B goes wrong. spring boot, jdbctemplate

我有一个方法,'databaseChanges',它以迭代方式调用 2 个操作:A、B。 'A' 第一,'B' 最后。 'A' & 'B' 可以是 Create, Update Delete我的持久存储 Oracle 数据库 11g 中的功能。

假设,

'A' 更新 table 用户中的一条记录,属性 zip,其中 id = 1。

'B' 在table 爱好中插入一条记录。

场景:调用databaseChanges方法,'A'操作并更新记录。 'B' 操作并尝试插入记录,发生异常,抛出异常,异常冒泡到databaseChanges方法。

预期: '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);
   }
}

问题:我没有连接(见问题post的标签)

我试过:

@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 class:

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。 我阅读并尝试了几个博客 posts 和 stackverflow 问答,但都没有成功解决我的问题。

有什么建议吗?


编辑

有一个调用 databaseChanges() 方法的方法

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

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

变化()? databaseChanges()?

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

@Transactional
public class MyFacade {

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

然后它应该正确回滚。您可以在此处找到更多详细信息 Does Spring @Transactional attribute work on a private method?

试试这个:

@TransactionManagement(TransactionManagementType.BEAN)
public class MyFacade {

@TransactionAttribute(TransactionAttribute.REQUIRES_NEW)
public void databaseChanges() throws Exception {   
    A(); //update.
    B(); //insert
}

@Transactional spring 注释的工作原理是将您的对象包装在代理中,代理又在事务中包装用 @Transactional 注释的方法。由于该注释不适用于私有方法(如您的示例所示),因为 私有方法不能被继承 => 它们不能被包装(如果您使用与 aspectj 的声明性交易,则以下与代理相关的警告不适用)。

这里是对@Transactionalspring魔法如何运作的基本解释。

您写道:

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 方法,因为它们在代理对象之前被调用。即使您配置正确,默认情况下,事务只会在 unchecked 异常时回滚。如果您需要回滚某些已检查的异常,请使用 @Transactional(rollbackFor={CustomCheckedException.class})

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

@Transactional 方法只有在您调用它 "from outside" 时才有效,在下面的示例中 b() 不会包装在事务中:

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

   @Transactional
   public void b() {
   }
}

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

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

public class A {
    @Autowired
    TransactionTemplate transactionTemplate;

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

...
} 

更新

正在使用以上信息回答 OP 更新的问题。

Which method should be annotated with @Transactional: changes()? databaseChanges()?

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

确保 changes() 被称为 bean 的 "from outside",而不是来自 class 本身并且在实例化上下文之后(例如,这不是 afterPropertiesSet()@PostConstruct 注释方法)。了解 spring 默认情况下仅针对未检查的异常回滚事务(尝试在 rollbackFor 已检查的异常列表中更具体)。

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

因此,您需要为您的项目做好以下准备:

Spring 事务依赖(以 Gradle 为例)

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

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

像这样

@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);
    }
};

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

的 public 方法上

还有关于 @Transactional 应该去哪里以及它的行为方式的高级主题,所以最好先让一些东西起作用,然后再探索这个领域:)

所有这些都到位后(依赖项+事务管理器配置+注释),事务应该相应地工作。

参考资料

Spring Reference Documentation on Transactions

Spring Guide for Transactions using Spring Boot - 这有您可以玩的示例代码

Any RuntimeException triggers rollback, and any checked Exception does not.

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

这取决于您在 databaseChanges 函数中遇到的异常。 因此,为了捕获所有异常,您需要做的就是添加 rollbackFor = Exception.class

应该在服务 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
    }
}