性能調(diào)優(yōu)!Spring Boot 選擇正確的事務方式,提升接口響應速度
環(huán)境:SpringBoot2.7.18
1. 編程方式與聲明方式
聲明式工作中用的最多,因為太過方便,你甚至都不需要做任何的配置。
@Transactional
public void save() {
// todo
}
編程方式,用的相對較少,因為比起聲明式要麻煩點(寫的代碼多了);但是如果能結(jié)合適當?shù)膱鼍澳敲催@種編程的方式會給你系統(tǒng)代理性能的提升。
@Resource
private TransactionTemplate template ;
public void save() {
// todo
template.execute(new TransactionCallback<Object>() {
public Object doInTransaction(TransactionStatus status) {
// todo
}
} ;
// todo
}
上面兩種對事務的使用方式非常明顯大家一般都不會選擇使用編程的方式。
編程事務應用場景
- 細粒度控制:編程式事務提供了對事務的細粒度控制。它允許開發(fā)人員在代碼中明確地定義事務的開始、提交和回滾,從而可以精確地控制事務的邊界和行為。這對于需要精確控制事務邏輯的場景非常有用,例如在復雜的業(yè)務邏輯中,可能需要根據(jù)不同的條件來決定是否提交或回滾事務。
- 非標準事務管理:當事務管理邏輯不符合標準的事務模型時,編程式事務是一個很好的選擇。例如,在某些特殊情況下,可能需要在一個方法中執(zhí)行多個數(shù)據(jù)庫操作,并且這些操作需要被劃分到不同的事務中。在這種情況下,如果使用了聲明式事務只能控制一個數(shù)據(jù)源,沒法對多個數(shù)據(jù)源進行控制。這里只是舉例,這種情況屬于分布式事務了,應該考慮如何保證事務的一致性了。
聲明式事務應用場景
- 簡化事務管理:聲明式事務通過注解定義事務規(guī)則,使得事務管理變得簡化。它不需要在業(yè)務邏輯代碼中顯式地編寫事務管理的代碼,從而減少了代碼的復雜性。
- 標準事務管理:聲明式事務通常用于標準的事務管理場景,例如數(shù)據(jù)庫的增刪改查操作。它提供了對事務的自動管理,包括自動提交和回滾事務,從而減少了開發(fā)人員對事務管理的關(guān)注。
2. 編程式事務應用
Spring提供了2中編程式的事務管理方式:
- 使用TransactionTemplate 或 TransactionalOperator
- 通過 TransactionManager
注意:TransactionTemplate是在命令式中使用,TransactionalOperator是在反應式中使用。
2.1 TransactionTemplate
在Spring Boot中如果你引入了如:data-jpa或者data-jdbc、data-r2dbc相關(guān)的依賴后,系統(tǒng)會自動的為我們配置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() ;
// 當發(fā)生異常后設置為回滾
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來進行相關(guān)屬性的配置,因為這是全局的,所有的操作都將使用這一份配置。
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);
}
}
在一個事務的上下文中運行。上面代碼執(zhí)行后你將在控制臺看到事務回滾信息。
圖片
如果沒有上面的as(transactionalOperator::transactional)操作,那么數(shù)據(jù)將會被正常的插入到數(shù)據(jù)庫中。
方式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. 性能對比(錯誤的應用事務)
數(shù)據(jù)庫連接配置
圖片
為了看到更好的效果,這里只配置了5個連接。
3.1 基于注解方式
業(yè)務方法
@Transactional
public void save(Person person) {
try {
// 模擬針對Person執(zhí)行其它非事務耗時操作
TimeUnit.SECONDS.sleep(1) ;
} catch (InterruptedException e) {}
this.personRepository.saveAndFlush(person) ;
}
通過jmeter測試;
線程池配置
圖片
測試結(jié)果
圖片
吞吐量非常低,并且還出現(xiàn)了錯誤。該錯誤是由于在30s內(nèi)沒有獲取到數(shù)據(jù)庫連接。
圖片
3.2 基于編程方式
業(yè)務方法
public void save(Person person) {
try {
// 模擬針對Person執(zhí)行其它非事務耗時操作
TimeUnit.SECONDS.sleep(1) ;
} catch (InterruptedException e) {}
this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
personRepository.saveAndFlush(person) ;
}
}) ;
}
jmeter配置不變,測試結(jié)果。
吞吐量大幅提升,并且沒有出現(xiàn)錯誤情況。
從上面的測試結(jié)果能夠充分的說明合理的使用事務方式在有些場景下是能夠非常明顯提升系統(tǒng)的整體性能。
總結(jié):非事務性的操作應該拿到事務外執(zhí)行,要么選擇編程事務。