环境:SpringBoot2.7.18
1. 编程方式与声明方式
声明式工作中用的最多,因为太过方便,你甚至都不需要做任何的配置。
@Transactional
public void save() {
// todo
}
编程方式,用的相对较少,因为比起声明式要麻烦点(写的代码多了);但是如果能结合适当的场景那么这种编程的方式会给你系统代理性能的提升。
@Resource
private TransactionTemplate template ;
public void save() {
// todo
template.execute(new TransactionCallback<Object>() {
public Object doInTransaction(TransactionStatus status) {
// todo
}
} ;
// todo
}
上面两种对事务的使用方式非常明显大家一般都不会选择使用编程的方式。
编程事务应用场景
- 细粒度控制:编程式事务提供了对事务的细粒度控制。它允许开发人员在代码中明确地定义事务的开始、提交和回滚,从而可以精确地控制事务的边界和行为。这对于需要精确控制事务逻辑的场景非常有用,例如在复杂的业务逻辑中,可能需要根据不同的条件来决定是否提交或回滚事务。
- 非标准事务管理:当事务管理逻辑不符合标准的事务模型时,编程式事务是一个很好的选择。例如,在某些特殊情况下,可能需要在一个方法中执行多个数据库操作,并且这些操作需要被划分到不同的事务中。在这种情况下,如果使用了声明式事务只能控制一个数据源,没法对多个数据源进行控制。这里只是举例,这种情况属于分布式事务了,应该考虑如何保证事务的一致性了。
声明式事务应用场景
- 简化事务管理:声明式事务通过注解定义事务规则,使得事务管理变得简化。它不需要在业务逻辑代码中显式地编写事务管理的代码,从而减少了代码的复杂性。
- 标准事务管理:声明式事务通常用于标准的事务管理场景,例如数据库的增删改查操作。它提供了对事务的自动管理,包括自动提交和回滚事务,从而减少了开发人员对事务管理的关注。
2. 编程式事务应用
Spring提供了2中编程式的事务管理方式:
- 使用TransactionTemplate 或 TransactionalOperator
- 通过 TransactionManager
注意:TransactionTemplate是在命令式中使用,TransactionalOperator是在反应式中使用。
2.1 TransactionTemplate
在Spring Boot中如果你引入了如:data-jpa或者data-jdbc、data-r2dbc相关的依赖后,系统会自动的为我们配置TransactionTemplate或TransactionalOperator
源码如下:
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(ReactiveTransactionManager.class)
public TransactionalOperator transactionalOperator(ReactiveTransactionManager transactionManager) {
return TransactionalOperator.create(transactionManager);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnSingleCandidate(PlatformTransactionManager.class)
public static class TransactionTemplateConfiguration {
@Bean
@ConditionalOnMissingBean(TransactionOperations.class)
public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {
return new TransactionTemplate(transactionManager);
}
}
TransactionTemplate完整使用示例:
@Resource
private TransactionTemplate template ;
@Resource
private JdbcTemplate jdbcTemplate ;
public void save(Person person) {
template.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus status) {
try {
int result = jdbcTemplate.update("insert into t_person (age, name) values (?, ?)", person.getAge(), person.getName());
} catch (Exception e) {
e.printStackTrace() ;
// 当发生异常后设置为回滚
status.setRollbackOnly() ;
}
return "success"
}
}) ;
}
上面示例是有返回值的情况,如果你不需要返回值则可以将TransactionCallback替换为TransactionCallbackWithoutResult 。
属性配置
public class PersonService {
private final TransactionTemplate transactionTemplate ;
public PersonService(PlatformTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
// 设置事务的隔离级别
this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
// 设置事务超时时间
this.transactionTemplate.setTimeout(30); // 30 seconds
}
}
注意:你最好不要使用Spring容器自动配置的TransactionTemplate来进行相关属性的配置,因为这是全局的,所有的操作都将使用这一份配置。
2.2 TransactionalOperator
方式1:
public class UserService {
@Resource
private R2dbcEntityTemplate template ;
private final TransactionalOperator transactionalOperator;
public UserService(ReactiveTransactionManager transactionManager) {
this.transactionalOperator = TransactionalOperator.create(transactionManager);
}
public Mono<User> save(User user) {
return Mono.just(user)
.then(template.insert(user))
.doOnNext(u -> {
// 人为的制造异常
System.out.println(1 / 0) ;
})
.as(transactionalOperator::transactional);
}
}
在一个事务的上下文中运行。上面代码执行后你将在控制台看到事务回滚信息。
图片
如果没有上面的as(transactionalOperator::transactional)操作,那么数据将会被正常的插入到数据库中。
方式2:
public Flux<Integer> save2(User user) {
return this.transactionalOperator.execute(new TransactionCallback<Integer>() {
@Override
public Mono<Integer> doInTransaction(ReactiveTransaction status) {
return Mono.just(user)
.then(template.insert(user))
.doOnNext(u -> {
System.out.println(1 / 0) ;
})
.doOnError(RuntimeException.class, e -> status.setRollbackOnly())
.map(User::getUid) ;
}
}) ;
}
2.3 TransactionManager
public void save() {
Person person = new Person();
person.setAge(36);
person.setName("张三");
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setName("CustomTx") ;
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED) ;
definition.setReadOnly(false) ;
definition.setTimeout(2) ;
TransactionStatus transactionStatus = tm.getTransaction(definition) ;
try {
jdbcTemplate.update("insert into t_person (age, name) values (?, ?)", person.getAge(), person.getName());
// 制造异常
System.out.println(1 / 0) ;
// 提交
tm.commit(transactionStatus) ;
} catch (Exception e) {
e.printStackTrace() ;
// 回滚
tm.rollback(transactionStatus) ;
}
}
3. 性能对比(错误的应用事务)
数据库连接配置
图片
为了看到更好的效果,这里只配置了5个连接。
3.1 基于注解方式
业务方法
@Transactional
public void save(Person person) {
try {
// 模拟针对Person执行其它非事务耗时操作
TimeUnit.SECONDS.sleep(1) ;
} catch (InterruptedException e) {}
this.personRepository.saveAndFlush(person) ;
}
通过jmeter测试;
线程池配置
图片
测试结果
图片
吞吐量非常低,并且还出现了错误。该错误是由于在30s内没有获取到数据库连接。
图片
3.2 基于编程方式
业务方法
public void save(Person person) {
try {
// 模拟针对Person执行其它非事务耗时操作
TimeUnit.SECONDS.sleep(1) ;
} catch (InterruptedException e) {}
this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
personRepository.saveAndFlush(person) ;
}
}) ;
}
jmeter配置不变,测试结果。
吞吐量大幅提升,并且没有出现错误情况。
从上面的测试结果能够充分的说明合理的使用事务方式在有些场景下是能够非常明显提升系统的整体性能。
总结:非事务性的操作应该拿到事务外执行,要么选择编程事务。