“我正在参与「启航方案」”

相信大家平常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了

自定义编程式事务