如果 B 出错,则回滚 A。弹簧启动,jdbctemplate

新手上路,请多包涵

我有一个方法“databaseChanges”,它以迭代方式调用 2 个操作:A、B。 “A”在前,“B”在后。 “ A ”和“ B ”可以在我的持久存储 Oracle 数据库 11g 中创建、更新和删除功能。

比方说,

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

‘B’ 在表 hobbies 中插入一条记录。

场景: 调用了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);
   }
}

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

我尝试过了:

 @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注解,

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

原文由 lolo 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 367
2 个回答

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

这是 @Transactional spring 魔法如何工作的基本解释。

你写了:

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

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

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

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

   @Transactional
   public void b() {
   }
}

这也是因为 @Transactional 通过代理您的对象来工作。在上面的示例 a() 将调用 X.b() 不是增强的“spring代理”方法 b() 所以不会有交易。作为解决方法,您必须从另一个 bean 调用 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();
}

确保 changes() 被称为 bean 的“外部”,而不是来自类本身并且在实例化上下文之后(例如,这不是 afterPropertiesSet()@PostConstruct ).了解默认情况下 spring rollbacks 事务仅针对未检查的异常(尝试在 rollbackFor checked exceptions 列表中更具体)。

原文由 featuredpeow 发布,翻译遵循 CC BY-SA 3.0 许可协议

任何 RuntimeException 触发回滚,任何检查的异常都不会。

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

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

原文由 choop 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题