zoukankan      html  css  js  c++  java
  • Spring Boot 声明式事务结合相关拦截器

      我这项目的读写分离方式在使用ThreadLocal实现的读写分离在迁移后的偶发错误里提了,我不再说一次了,这次是有要求读写分离与事务部分要完全脱离配置文件,程序员折腾了很久,于是我就查了一下,由于我还是比较喜欢使用xml的方式,所以就随便。。。(过程省略吧),然而,似乎是一定要声明式的方式,所以,无奈之下就只好干了。

      首先,在之前的博客里提到过,我们的读写分离方式要求我们自己的AOP拦截器必须在事务拦截器之前执行,在配置文件的方式下很容易,在aop的配置里设置一下Order就好了。然而,Spring Boot的@EnableTransactionManagement注解中已经把这部分固定了,官方文档似乎说它是和@Transactional配合使用的,总之几乎没有留下什么插手的余地(如果大家有好办法,希望能告诉我一下):

      这个ProxyTransactionManagementConfiguration类中,就直接手new了TransactionInterceptor:

        public TransactionInterceptor transactionInterceptor() {
            TransactionInterceptor interceptor = new TransactionInterceptor();
            interceptor.setTransactionAttributeSource(transactionAttributeSource());
            if (this.txManager != null) {
                interceptor.setTransactionManager(this.txManager);
            }
            return interceptor;
        }

      虽然这里可以通过上图的方式加点什么,但这个事务体系本身是非常独立的,而且在启动过程中就已经确定下来了,既然要调节它和自定义的aop的执行顺序,我想只能先统一他们的执行策略。之前自定义的aop是在启动过程中就被加入到一个拦截器的调用链中:

      由于Spring Boot很多东西并没有留什么扩展的余地(就好像前面那个new),如果完全不用配置文件,使用Spring Boot的方式,虽然没有什么顺眼的方法,其实也还是能做的,先提个建议,能不用的情况下,最好不要用。方法是自定义一个事务拦截器,抛弃@EnableTransactionManagement,测试代码如下,不要在意命名,图方便直接在原来的上面改的:

        @Bean(name = "newDataSourceAop")
        public NewDataSourceAop newDataSourceAop(){
            return new NewDataSourceAop();
        }
    
        @Bean(name = "tInterceptor")
        public TInterceptor tInterceptor(){
            return new TInterceptor();
        }
    
        /**
         * 代理
         * @return
         */
        @Bean
        public BeanNameAutoProxyCreator transactionAutoProxy() {
            BeanNameAutoProxyCreator autoProxy = new BeanNameAutoProxyCreator();
            autoProxy.setProxyTargetClass(true);// 这个属性为true时,表示被代理的是目标类本身而不是目标类的接口
            autoProxy.setBeanNames("*ServiceImpl");
            autoProxy.setInterceptorNames("newDataSourceAop", "tInterceptor");
            return autoProxy;
        }

      注意,拦截器的顺序依赖于名字字符串传入的先后顺序,@Order什么的是完全没用的,因为保存这些拦截器的是一个字符串数组。自定义的事务AOP Advice:

    public class TInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
        
        public TInterceptor() {
            setTransactionAttributes(getAttrs());
        }
        
        private Properties getAttrs(){
            Properties attributes = new Properties();
            // 新增
            attributes.setProperty("create*", "PROPAGATION_REQUIRED,ISOLATION_DEFAULT");
            // 修改
            attributes.setProperty("update*", "PROPAGATION_REQUIRED,ISOLATION_DEFAULT");
            // 删除
            attributes.setProperty("delete*", "PROPAGATION_REQUIRED,ISOLATION_DEFAULT");
            //查询
            attributes.setProperty("query*", "PROPAGATION_REQUIRED,ISOLATION_DEFAULT");
            return attributes;
        }
        
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
        
            // Adapt to TransactionAspectSupport's invokeWithinTransaction...
            return invokeWithinTransaction(invocation.getMethod(), targetClass, () -> invocation.proceed());
        }
    }

      自定义的读写分离Advice:

    @EnableConfigurationProperties(ReadDBPathProperties.class)
    public class NewDataSourceAop implements MethodBeforeAdvice {
        private static final Logger log = LoggerFactory.getLogger(NewDataSourceAop.class);
        
        @Autowired
        private ReadDBPathProperties readDBPathProperties;
    
        @Override
        public void before(Method method, Object[] args, Object target) throws Throwable {
            String clazzName = method.getDeclaringClass().getSimpleName();
            String runner = clazzName + "." + method.getName();
            this.chooseDataSource(runner);
        }
    
        private void chooseDataSource(String runner){
            runner += ",";
            String read = readDBPathProperties.getReadPath()+",";
            log.info("case : read path, vo : readPath = {}", read);
            int index = read.indexOf(runner);
            if (index == -1){
                log.info("case : choose write DB, runner : {}, tid={}", runner, Thread.currentThread().getId());
                HandleDataSource.putDataSource("write");
                return;
            }
            log.info("case : choose read DB, runner : {}, tid={}", runner, Thread.currentThread().getId());
            HandleDataSource.putDataSource("read");
        }
    }

      最后再特别说一下,关于这个功能的单元测试,这个单元测试有一点意思,如果是直接运行测试方法,启动过程和执行过程在同一个线程是测试不出效果的,因为启动过程中加载Bean的时候会对下图中的resources初始化,写入默认的数据源:

      就会导致后续执行的读写分离拦截器失效,只要保证执行线程和启动线程不在同一线程就好。

    ==========================================================

    咱最近用的github:https://github.com/saaavsaaa

    微信公众号:

                          

  • 相关阅读:
    如何使用SAP Intelligent Robotic Process Automation自动操作Excel
    OpenSAML 使用引导 IV: 安全特性
    Spring Cloud Zuul 网关使用与 OAuth2.0 认证授权服务
    微服务架构集大成者—Spring Cloud (转载)
    Spring Cloud Eureka 服务注册列表显示 IP 配置问题
    使用 Notification API 开启浏览器桌面提醒
    SignalR 中使用 MessagePack 序列化提高 WebSocket 通信性能
    配置 Nginx 的目录浏览功能
    关于 Nginx 配置 WebSocket 400 问题
    Migrate from ASP.NET Core 2.0 to 2.1
  • 原文地址:https://www.cnblogs.com/saaav/p/6905481.html
Copyright © 2011-2022 走看看