文档章节

Spring Boot 声明式事务结合相关拦截器

drv
 drv
发布于 2017/05/26 19:55
字数 983
阅读 226
收藏 0

  我这项目的读写分离方式在使用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

微信公众号:

                      

转载请注明出处

© 著作权归作者所有

drv

drv

粉丝 2
博文 57
码字总数 75382
作品 0
东城
架构师
私信 提问
Spring编程式和声明式事务

1.编程式事务 1.1 编程式和声明式事务的区别 Spring提供了对编程式事务和声明式事务的支持,编程式事务允许用户在代码中精确定义事务的边界,而声明式事务(基于AOP)有助于用户将操作与事务...

梨加橙
2018/06/19
73
0
分布式事务系列(1.2)Spring的事务体系

1 系列目录 - 分布式事务系列(开篇)提出疑问和研究过程- 分布式事务系列(1.1)Spring事务管理器PlatformTransactionManager源码分析- 分布式事务系列(1.2)Spring事务体系- 分布式事务系...

乒乓狂魔
2015/05/18
4.7K
4
220.详细整理学习spring boot

1.springboot是什么? 有什么用? 1.1 是什么 一个整合常用第三方框架,简化xml配置,完全采用注解形式,内置tomcat容器,帮助开发者快速实现项目搭建,spring boot 的web组件默认集成的是spr...

Lucky_Me
04/23
90
1
spring事务管理源码解析之其他

说在前面 基于注解的spring声明式事务管理源码解析已经完毕了,第一篇文章中提到spring事务管理模式有两种形式一种是proxy,一种是aspectj,基于proxy已经解析完毕了,默认的也是proxy,声明...

天河2018
2018/07/09
0
0
Spring Boot实战之基础回顾

本文作者: 吴伟祥 本文链接: https://wuweixiang.cn/2018/08/21/Spring-Boot实战之基础回顾/ 版权声明: 本博客所有文章除特别声明外均为原创,采用CC BY-NC-SA 4.0 许可协议。转载请在文章开...

吴伟祥
2018/08/21
67
0

没有更多内容

加载失败,请刷新页面

加载更多

rime设置为默认简体

转载 https://github.com/ModerRAS/ModerRAS.github.io/blob/master/_posts/2018-11-07-rime%E8%AE%BE%E7%BD%AE%E4%B8%BA%E9%BB%98%E8%AE%A4%E7%AE%80%E4%BD%93.md 写在开始 我的Arch Linux上......

zhenruyan
今天
5
0
简述TCP的流量控制与拥塞控制

1. TCP流量控制 流量控制就是让发送方的发送速率不要太快,要让接收方来的及接收。 原理是通过确认报文中窗口字段来控制发送方的发送速率,发送方的发送窗口大小不能超过接收方给出窗口大小。...

鏡花水月
今天
10
0
OSChina 周日乱弹 —— 别问,问就是没空

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @tom_tdhzz :#今日歌曲推荐# 分享容祖儿/彭羚的单曲《心淡》: 《心淡》- 容祖儿/彭羚 手机党少年们想听歌,请使劲儿戳(这里) @wqp0010 :周...

小小编辑
今天
1K
11
golang微服务框架go-micro 入门笔记2.1 micro工具之micro api

micro api micro 功能非常强大,本文将详细阐述micro api 命令行的功能 重要的事情说3次 本文全部代码https://idea.techidea8.com/open/idea.shtml?id=6 本文全部代码https://idea.techidea8....

非正式解决方案
今天
5
0
Spring Context 你真的懂了吗

今天介绍一下大家常见的一个单词 context 应该怎么去理解,正确的理解它有助于我们学习 spring 以及计算机系统中的其他知识。 1. context 是什么 我们经常在编程中见到 context 这个单词,当...

Java知其所以然
昨天
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部