如果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 }
您似乎缺少的是TransactionManager
。 TransactionManager
的目的是能够管理数据库事务。 有两种类型的交易,程序化和声明式。 您所描述的是需要通过注释进行声明性事务。
那么您需要为项目准备的是以下内容:
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