“我正在参与「启航方案」”
相信大家平常CRUD肯定少不了用业务处理问题,提到业务第一个想到的肯定是 @Transactional 注解,这玩意方便,放到办法头就能开盖即食,可是方便的同时也有一些坏处,最近就刚好碰到业务常见的一个问题:长业务。
先提早预热一下业务的相关知识
1、什么是业务
业务是数据一致性最基本的确保,也便是说一个业务中的操作要么都成功,要么都失败,不允许部分成功。我们常说的业务便是jdbc业务
2、业务的传播特点
1) REQUIRED(默许特点)
假如存在一个业务,则支撑当时业务。假如没有业务则敞开一个新的业务。 被设置成这个等级时,会为每一个被调用的办法创立一个逻辑业务域。假如前面的办法现已创立了业务,那么后边的办法支撑当时的业务,假如当时没有业务会重新树立业务。
2) MANDATORY
支撑当时业务,假如当时没有业务,就抛出反常。
3) NEVER
以非业务办法履行,假如当时存在业务,则抛出反常。
4) NOT_SUPPORTED
以非业务办法履行操作,假如当时存在业务,就把当时业务挂起。
5) REQUIRES_NEW
新建业务,假如当时存在业务,把当时业务挂起。
6) SUPPORTS
支撑当时业务,假如当时没有业务,就以非业务办法履行。
7) NESTED
支撑当时业务,新增Savepoint点,与当时业务同步提交或回滚。
嵌套业务一个非常重要的概念便是内层业务依赖于外层业务。外层业务失败时,会回滚内层业务所做的动作。而内层业务操作失败并不会引起外层业务的回滚。
3、长业务的危害
一个业务假如过长会有什么影响?简单来说,在业务被敞开到commit停止,数据库会一向占用锁资源,其次所有的业务视图会一向保存着,暂用存储空间,假如涉及到大量数据改变或io衔接的话,就愈加难堪了,最严峻的便是数据库挂掉,这要是到生产影响可就不是一毛两毛的事了
4、处理方案
处理长业务第一个想到的便是缩小业务的粒度,把需求操控业务的办法抽出来独立加业务处理;@Transactional 也被称为声明式业务办理,经过AOP的办法由Spring容器会集办理,另一种便是编程式业务办理了,能够自由的操控业务的规模,尽管没有@Transactional 那么舒畅,可是至少透明可控啊,代码如下 处理长业务第一个想到的便是缩小业务的粒度,把需求操控业务的办法抽出来独立加业务处理;@Transactional** 也被称为声明式业务办理,经过AOP的办法由Spring容器会集办理,另一种便是编程式业务办理了,能够自由的操控业务的规模,尽管没有@Transactional 那么舒畅,可是至少透明可控啊,代码如下
界说一个履行接口
public interface TransactionCallBack { T doInTransaction(TransactionStatus status) throws Exception; }
界说编程式业务工具
@Component
@Slf4j
public class TransactionTemplate extends DefaultTransactionDefinition implements InitializingBean {
/**
* 业务办理器
*/
@Autowired
private PlatformTransactionManager transactionManager;
@Override
public void afterPropertiesSet() {
// 校验办理器是否被spring注入
if (this.transactionManager == null) {
throw new IllegalArgumentException("Property 'transactionManager' is required");
}
}
/**
* 业务履行器
* @param action
* @param <T>
* @return
*/
@Transactional
public <T> T execute(TransactionCallBack<T> action){
TransactionStatus status = this.transactionManager.getTransaction(this);
T result = null;
try {
result = action.doInTransaction(status);
}catch (Exception e){
// 业务回滚
this.transactionManager.rollback(status);
log.error("业务履行反常",e);
return result;
}
// 业务提交
this.transactionManager.commit(status);
return result;
}
}
假如不声明传播特点的话,默许是REQUIRED,当然也能够根据实际情况设置不同的办法
@Component
public class CustomizeTransactionTemplate extends TransactionTemplate{
/**
* 设置业务传播行为
* PROPAGATION_REQUIRED : 假如存在一个业务,则支撑当时业务。假如没有业务则敞开一个新的业务
* PROPAGATION_MANDATORY : 支撑当时业务,假如当时没有业务,就抛出反常。
* PROPAGATION_NEVER : 以非业务办法履行,假如当时存在业务,则抛出反常。
* PROPAGATION_NOT_SUPPORTED : 以非业务办法履行操作,假如当时存在业务,就把当时业务挂起。
* PROPAGATION_REQUIRES_NEW : 新建业务,假如当时存在业务,把当时业务挂起。
* PROPAGATION_SUPPORTS : 支撑当时业务,假如当时没有业务,就以非业务办法履行。
* PROPAGATION_NESTED : 支撑当时业务,新增Savepoint点,与当时业务同步提交或回滚。
*
* @return
* @author gqq 2022/9/27 - 15:50
**/
public void setPropagationName(String constantName){
this.setPropagationBehaviorName(constantName);
}
}
测验类如下
public class DemoTest {
@Autowired
private StoreInfoMapper storeInfoMapper;
@Autowired
private CustomizeTransactionTemplate transactionTemplate;
public void transactionTest(){
transactionTemplate.execute(status -> {
StoreInfo storeInfo = storeInfoMapper.selectById(1);
storeInfo.setUpdateTime(new Date());
// 更新时间
storeInfoMapper.updateById(storeInfo);
System.out.println(1/0);
return status;
});
}
}
履行成果
2022-09-27 17:36:59.005 ERROR 12324 --- [ main] c.c.f.c.s.t.TransactionTemplate : 业务履行反常
java.lang.ArithmeticException: / by zero
at com.food.service.sub.job.DemoTest.lambda$transactionTest$0(DemoTest.java:34) [classes/:na]
at com.food.common.starter.transaction.TransactionTemplate.execute(TransactionTemplate.java:47) ~[classes/:na]
at com.food.common.starter.transaction.TransactionTemplate$$FastClassBySpringCGLIB$$508a8f86.invoke(<generated>) [classes/:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) [spring-core-5.3.8.jar:5.3.8]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779) [spring-aop-5.3.8.jar:5.3.8]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) [spring-aop-5.3.8.jar:5.3.8]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) [spring-aop-5.3.8.jar:5.3.8]
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) [spring-tx-5.3.8.jar:5.3.8]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) [spring-tx-5.3.8.jar:5.3.8]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) [spring-tx-5.3.8.jar:5.3.8]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) [spring-aop-5.3.8.jar:5.3.8]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) [spring-aop-5.3.8.jar:5.3.8]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692) [spring-aop-5.3.8.jar:5.3.8]
......
因为1/0的原因,当然这个sql是不会被更新的,直接回滚,用到后边会发现,其实编程式业务办理仍是很香的,只需注入CustomizeTransactionTemplate就能够直接运用
5、总结
业务还有很深的学识,这儿就不一一多说了,平常开发仍是要尽量避免长业务的代码,减少业务粒度,就能少吃点bug了