当前位置: 首页 > 知识库问答 >
问题:

如果B出错,请回滚A。Spring Boot,jdbctemplate

邵伟泽
2023-03-14

我有一个方法,数据库变化,它以迭代的方式调用2个操作:A,B。第一个,最后一个。'A'

让我们说,

'a'更新表用户属性zip中的记录,其中id=1。

“B”在表中插入记录。

场景:数据库更改方法被调用,'A'操作并更新记录。'B'操作并尝试插入一条记录,发生了一些事情,抛出了一个异常,异常正在冒泡到数据库更改方法。

预期:“A”和“B”没有改变任何东西。“A”执行的更新将被回滚。”B'没有改变什么,嗯。。。有一个例外。

实际:“更新似乎未回滚。”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 Q

有什么建议吗?

编辑

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

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

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

变化()?数据库更改

共有3个答案

尹赞
2023-03-14

您呈现的第一个代码是用于UserTransaction的,即应用程序必须执行事务管理。通常,您希望容器处理这些问题,并使用@Transactional注释。我认为在你的情况下,问题可能是,你有一个私有方法的注释。我会把注释移到班级级别

@Transactional
public class MyFacade {

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

然后它应该正确地回滚。您可以在这里找到更多详细信息Spring@Transactional属性是否在私有方法上工作?

海新霁
2023-03-14

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

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

这取决于您在数据库更改函数中获得的异常。因此,为了捕获所有异常,您需要做的就是添加rollback For=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
    }
}

此外,你可以用它做一些好事,所以不是所有的时间,你将不得不写rollback For=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
    }
}
傅峰
2023-03-14

spring中的@Transactional注释通过将对象包装在代理中来工作,代理又将使用@Transactional注释的方法包装在事务中。因为这个注释在私有方法上不起作用(如您的示例中),因为私有方法不能被继承=

下面是关于@Transactionalspring magic如何工作的基本解释。

你写道:

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(rollboor={CustomCheckedException.class})

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

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

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

   @Transactional
   public void b() {
   }
}

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

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

public class A {
    @Autowired
    TransactionTemplate transactionTemplate;

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

...
} 

使现代化

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

哪个方法应该被注释为@Transactional:更改()?数据库更改

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

确保changes()是从bean的“外部”调用的,而不是从类本身以及在实例化上下文之后调用的(例如,这不是afterPropertieSet()@PostConstruct带注释的方法)。了解spring在默认情况下仅对未检查的异常回滚事务(请尝试在rollbackFor checked exceptions列表中更加具体)。

 类似资料:
  • 问题内容: 我有一个方法“ databaseChanges”,它以迭代方式调用2个操作:A,B。首先是“ A”,最后是“ B”。“A”和“B”可以是 Ç reate, ü PDATE d 在我的持久存储elete功能,Oracle数据库11g。 比方说 ‘A’更新表Users(属性zip,其中id = 1)中的记录。 “ B”在表爱好中插入一条记录。 场景: 调用了databaseChanges方

  • 本文向大家介绍分析`('b' + 'a' + +'a' + 'a').toLowerCase()`返回的结果相关面试题,主要包含被问及分析`('b' + 'a' + +'a' + 'a').toLowerCase()`返回的结果时的应答技巧和注意事项,需要的朋友参考一下 那个多出来的 + 是一元操作符,操作数是后面那个 'a',它被转成了 number 。 因此加出来是 'baNaNa',toLo

  • 问题内容: 这是我的第一个问题,我开始学习Python。之间有什么区别: 和 在下面的示例中编写时,它显示不同的结果。 和 问题答案: 在中,在将右侧的表达式赋给左侧之前对其求值。因此,它等效于: 在第二个示例中,运行时已更改的值。因此,结果是不同的。

  • 我尝试了一些代码在Java中交换两个整数,而不使用第三个变量,即使用XOR。 以下是我尝试的两个交换函数: 该代码产生的输出如下: 我很想知道,为什么会有这样的说法: 和这个不一样?

  • 问题内容: 我尝试了一些代码,使用XOR在Java中交换两个整数而不使用第三个变量。 这是我尝试的两个交换函数: 这段代码产生的输出是这样的: 我很好奇,为什么这样说: 与这个不同吗? 问题答案: 问题是评估的顺序: 参见JLS第15.26.2节 首先,对左操作数求值以产生一个变量。 如果该评估突然完成,则赋值表达式由于相同的原因而突然完成;右边的操作数不会被评估,并且不会发生赋值。 否则,将保存

  • 问题内容: 今天,我发现了python语言一个有趣的“功能”,这让我感到非常悲伤。 那个怎么样?我以为两者是等同的!更糟糕的是,这是我调试时遇到的麻烦的代码 WTF!我的代码中包含列表和字典,并且想知道我到底怎么把dict的键附加到列表上而又没有调用.keys()。事实证明,这就是方法。 我认为这两个陈述是等效的。即使忽略这一点,我也可以理解将字符串追加到列表的方式(因为字符串只是字符数组),但是