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

lol*_*olo 12 java oracle jdbctemplate spring-transactions spring-boot

我有一个方法'databaseChanges',它以迭代的方式调用2个操作:A,B.'A'第一,'B'最后."A"和"B"可以是Ç reate,û PDATE d elete功能在我的永久存储,Oracle数据库11g.

比方说,

'A'更新表Users中的记录,属性zip,其中id = 1.

'B'在表爱好中插入记录.

场景:已调用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);
   }
}
Run Code Online (Sandbox Code Playgroud)

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

我试过了:

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

    @Transactional
    private void databaseChanges() throws Exception {   
        A(); //update.
        B(); //insert
    }
}
Run Code Online (Sandbox Code Playgroud)

我的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);
    }   
}
Run Code Online (Sandbox Code Playgroud)

'A'进行更新.来自'B'的例外被抛出.由'A'进行的更新未被回滚.

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

有什么建议?


编辑

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

public void changes() throws Exception {
    someLogicBefore();
    databaseChanges();
    someLogicAfter();
}
Run Code Online (Sandbox Code Playgroud)

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

变化()?databaseChanges()?

fre*_*ger 20

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

这是@Transactional春天魔法如何运作的基本解释.

你写了:

class A {
    @Transactional
    public void method() {
    }
}
Run Code Online (Sandbox Code Playgroud)

但这是你注入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
       }
   }
} 
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

   @Transactional
   public void b() {
   }
}
Run Code Online (Sandbox Code Playgroud)

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

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

public class A {
    @Autowired
    TransactionTemplate transactionTemplate;

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

...
} 
Run Code Online (Sandbox Code Playgroud)

更新

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

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

@Transactional(rollbackFor={Exception.class})
public void changes() throws Exception {
    someLogicBefore();
    databaseChanges();
    someLogicAfter();
}
Run Code Online (Sandbox Code Playgroud)

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


cho*_*oop 7

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

这是所有Spring事务API的共同行为。默认情况下,如果RuntimeException从事务代码内抛出a ,则事务将回滚。如果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
    }
}
Run Code Online (Sandbox Code Playgroud)

另外,您可以使用它做一些不错的事情,因此不必每次都写rollbackFor = Exception.class。您可以通过编写自己的自定义注释来实现:

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

最终代码将如下所示:

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

    @CustomTransactional
    private void databaseChanges() throws Exception {   
        A(); //update
        B(); //insert
    }
}
Run Code Online (Sandbox Code Playgroud)