如果 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 的声明性交易,则以下与代理相关的警告不适用)。
这里是对@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
方法,因为它们在代理对象之前被调用。即使您配置正确,默认情况下,事务只会在 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 已检查的异常列表中更具体)。
您似乎缺少 TransactionManager
。 TransactionManager
的目的是能够管理数据库事务。有两种类型的交易,编程式和声明式。您所描述的是需要通过注释进行声明性交易。
因此,您需要为您的项目做好以下准备:
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
}
}
我有一个方法,'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 的声明性交易,则以下与代理相关的警告不适用)。
这里是对@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
方法,因为它们在代理对象之前被调用。即使您配置正确,默认情况下,事务只会在 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 已检查的异常列表中更具体)。
您似乎缺少 TransactionManager
。 TransactionManager
的目的是能够管理数据库事务。有两种类型的交易,编程式和声明式。您所描述的是需要通过注释进行声明性交易。
因此,您需要为您的项目做好以下准备:
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()
.
还有关于 @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
}
}