Spring主从数据源动态切换
- 不同类型数据源都可能存在master和slave区分;
 - 数据源之间已经可以通过package区分,不同package对应的service也不同;
 - aop在service层面,对应不同数据源的service之间可能存在互相调用;
 - 最外层方法的名称决定了该数据源应该使用master(可写)还是slave数据源(不可写);
 - 在嵌套使用其他service的过程中,根据情况分析该service方法是否使用slave数据源;
 
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="create*" propagation="REQUIRED"/>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="merge*" propagation="REQUIRED"/>
<tx:method name="del*" propagation="REQUIRED"/>
<tx:method name="remove*" propagation="REQUIRED"/>
<!--hibernate4必须配置为开启事务 否则 getCurrentSession()获取不到 -->
<tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="count*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="list*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice> <!-- 只对业务逻辑层实施事务 -->
<aop:config expose-proxy="true">
<aop:pointcut id="txPointcut" expression="(execution(* com.api.example.*.*.service..*.*(..)))
or (execution(* com.api.example.*.service..*.*(..)))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
实现方案

public class MasterSlaveDataSource extends AbstractDataSource implements InitializingBean {
    private static final Logger log = LoggerFactory.getLogger(MasterSlaveDataSource.class);
    private DataSource masterDataSource;
    private Map<String, DataSource> slaveDataSourceMap;
@Override
public Connection getConnection() throws SQLException {
return determineDataSource().getConnection();
} @Override
public Connection getConnection(String username, String password) throws SQLException {
return determineDataSource().getConnection(username, password);
}
public DataSource determineDataSource() {
        if (masterSlaveDataSourceDecision.isChoiceWrite()) {
            log.debug("current determine write datasource");
            return masterDataSource;
        } else if (masterSlaveDataSourceDecision.isChoiceNone()) {
            log.debug("no choice read/write, default determine write datasource");
            return masterDataSource;
        } else {
            return selectReadDataSource();
        }
    }
public class MasterSlaveDataSourceProcessor implements BeanPostProcessor {
    private Map<String, Boolean> readWriteMethodMap = new HashMap<String, Boolean>();
private String txAdviceName;
    private MasterSlaveDataSourceDecision masterSlaveDataSourceDecision;
    public void setTxAdviceName(String txAdviceName) {
        this.txAdviceName = txAdviceName;
    }
    public void setMasterSlaveDataSourceDecision(MasterSlaveDataSourceDecision masterSlaveDataSourceDecision) {
        this.masterSlaveDataSourceDecision = masterSlaveDataSourceDecision;
    }
@Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (txAdviceName.equalsIgnoreCase(beanName)) {
            try {
                TransactionInterceptor transactionInterceptor = (TransactionInterceptor) bean;
                NameMatchTransactionAttributeSource transactionAttributeSource =
                        (NameMatchTransactionAttributeSource) transactionInterceptor.getTransactionAttributeSource();
                Field nameMapField = ReflectionUtils.findField(NameMatchTransactionAttributeSource.class, "nameMap");
                nameMapField.setAccessible(true);
                Map<String, TransactionAttribute> nameMap = (Map<String, TransactionAttribute>) nameMapField
                        .get(transactionAttributeSource);
                for (Entry<String, TransactionAttribute> entry : nameMap.entrySet()) {
                    RuleBasedTransactionAttribute attr = (RuleBasedTransactionAttribute) entry.getValue();
                    // 仅对read-only的处理
                    String methodName = entry.getKey();
                    if (attr.isReadOnly()) {
                        if (forceChoiceReadWhenWrite) {
                            // 不管之前操作是写,默认强制从读库读 (设置为NOT_SUPPORTED即可)
                            // NOT_SUPPORTED会挂起之前的事务
                            attr.setPropagationBehavior(Propagation.NOT_SUPPORTED.value());
                        } else {
                            // 否则 设置为SUPPORTS(这样可以参与到写事务)
                            attr.setPropagationBehavior(Propagation.SUPPORTS.value());
                        }
                    }
                    log.info("read/write transaction process  method:{} force read:{}", methodName,
                            forceChoiceReadWhenWrite);
                    readWriteMethodMap.put(methodName, attr.isReadOnly());
                }
            } catch (Exception e) {
                throw new ReadWriteDataSourceTransactionException("process read/write transaction error", e);
            }
        }
        return bean;
    }
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="create*" propagation="REQUIRED"/>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="merge*" propagation="REQUIRED"/>
<tx:method name="del*" propagation="REQUIRED"/>
<tx:method name="remove*" propagation="REQUIRED"/>
<tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="count*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="list*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="*" propagation="SUPPORTS" read-only="true"/>
public Object selectDataSource(ProceedingJoinPoint pjp) throws Throwable {
        if (isChoiceReadDB(pjp.getSignature().getName())) {
            masterSlaveDataSourceDecision.markRead();
        } else {
            masterSlaveDataSourceDecision.markWrite();
        }
        try {
            return pjp.proceed();
        } catch (Throwable t) {
            masterSlaveDataSourceDecision.reset();
            throw t;
        } finally {
            masterSlaveDataSourceDecision.pop();
        }
    }
private boolean isChoiceReadDB(String methodName) {
        String bestNameMatch = null;
        for (String mappedName : this.readWriteMethodMap.keySet()) {
            if (PatternMatchUtils.simpleMatch(mappedName, methodName)&&
                    (bestNameMatch == null || bestNameMatch.length() <= mappedName.length())) {
                bestNameMatch = mappedName;
            }
        }
        // 默认走写库
        boolean currentRead = (bestNameMatch == null ? false : readWriteMethodMap.get(bestNameMatch));
        // 如果当前为读库,并且设置了强制读,则忽略当前主库写状态
        if (currentRead && forceChoiceReadWhenWrite) {
            return true;
        }
        // 如果之前选择了写库,则当前使用写库
        if (masterSlaveDataSourceDecision.isChoiceWrite()) {
            return false;
        }
        return currentRead;
    }
public class MasterSlaveDataSourceDecision {
    public enum DataSourceType {
        write, read;
    }
    private final ThreadLocal<Stack<DataSourceType>> holder = new ThreadLocal<Stack<DataSourceType>>() {
        @Override
        protected Stack<DataSourceType> initialValue() {
            return new Stack<>();
        }
    };
    public void markWrite() {
        holder.get().push(DataSourceType.write);
    }
    public void markRead() {
        holder.get().push(DataSourceType.read);
    }
    public void reset() {
        holder.get().clear();
    }
    public boolean isChoiceNone() {
        return holder.get().isEmpty();
    }
    public boolean isChoiceWrite() {
        return !isChoiceNone() && DataSourceType.write == holder.get().peek();
    }
    public boolean isChoiceRead() {
        return !isChoiceNone() && DataSourceType.read == holder.get().peek();
    }
    public DataSourceType pop() {
        return isChoiceNone() ? null : holder.get().pop();
    }
}
配置方法
<bean id="masterSlaveDataSource" class="com.api.example.tx.MasterSlaveDataSource">
<property name="masterSlaveDataSourceDecision" ref="masterSlaveDataSourceDecision"/>
<property name="masterDataSource" ref="dataSource"/>
<property name="slaveDataSourceMap">
<map><entry key="slave1" value-ref="dataSourceSlave"/></map>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="masterSlaveDataSource"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="create*" propagation="REQUIRED"/>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="merge*" propagation="REQUIRED"/>
<tx:method name="del*" propagation="REQUIRED"/>
<tx:method name="remove*" propagation="REQUIRED"/>
<!--hibernate4必须配置为开启事务 否则 getCurrentSession()获取不到 -->
<tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="count*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="list*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 只对业务逻辑层实施事务 -->
<aop:config expose-proxy="true">
<aop:pointcut id="txPointcut" expression="execution(* com.api.example.service..*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/> <aop:aspect order="-1" ref="masterSlaveDataSourceProcessor">
<aop:around method="selectDataSource" pointcut-ref="txPointcut"/>
</aop:aspect>
</aop:config>
<bean id="masterSlaveDataSourceProcessor" class="com.api.example.tx.MasterSlaveDataSourceProcessor">
<property name="txAdviceName" value="txAdvice"/>
<property name="masterSlaveDataSourceDecision" ref="masterSlaveDataSourceDecision"/>
</bean> <bean id="masterSlaveDataSourceDecision" class="com.api.example.tx.MasterSlaveDataSourceDecision"/>
Spring主从数据源动态切换的更多相关文章
- Spring多数据源动态切换
		
title: Spring多数据源动态切换 date: 2019-11-27 categories: Java Spring tags: 数据源 typora-root-url: ...... --- ...
 - spring 多数据源动态切换
		
理解spring动态切换数据源,需要对spring具有一定的了解 工作中经常遇到读写分离,数据源切换的问题,那么以下是本作者实际工作中编写的代码 与大家分享一下! 1.定义注解 DataSource ...
 - Spring Boot 如何动态切换数据源
		
本章是一个完整的 Spring Boot 动态数据源切换示例,例如主数据库使用 lionsea 从数据库 lionsea_slave1.lionsea_slave2.只需要在对应的代码上使用 Data ...
 - 实战:Spring AOP实现多数据源动态切换
		
需求背景 去年底,公司项目有一个需求中有个接口需要用到平台.算法.大数据等三个不同数据库的数据进行计算.组装以及最后的展示,当时这个需求是另一个老同事在做,我只是负责自己的部分. 直到今年回来了,这个 ...
 - Spring3.3 整合 Hibernate3、MyBatis3.2 配置多数据源/动态切换数据源 方法
		
一.开篇 这里整合分别采用了Hibernate和MyBatis两大持久层框架,Hibernate主要完成增删改功能和一些单一的对象查询功能,MyBatis主要负责查询功能.所以在出来数据库方言的时候基 ...
 - Springboot多数据源配置--数据源动态切换
		
在上一篇我们介绍了多数据源,但是我们会发现在实际中我们很少直接获取数据源对象进行操作,我们常用的是jdbcTemplate或者是jpa进行操作数据库.那么这一节我们将要介绍怎么进行多数据源动态切换.添 ...
 - springboot多数据源动态切换和自定义mybatis分页插件
		
1.配置多数据源 增加druid依赖 完整pom文件 数据源配置文件 route.datasource.driver-class-name= com.mysql.jdbc.Driver route.d ...
 - Spring3.3 整合 Hibernate3、MyBatis3.2 配置多数据源/动态切换数据源方法
		
一.开篇 这里整合分别采用了Hibernate和MyBatis两大持久层框架,Hibernate主要完成增删改功能和一些单一的对象查询功能,MyBatis主要负责查询功能.所以在出来数据库方言的时候基 ...
 - mybatis 多数据源动态切换
		
笔者主要从事c#开发,近期因为项目需要,搭建了一套spring-cloud微服务框架,集成了eureka服务注册中心. gateway网关过滤.admin服务监控.auth授权体系验证,集成了redi ...
 
随机推荐
- Recover Binary Search Tree,恢复二叉排序树
			
问题描述:题意就是二叉树中有两个节点交换了,恢复结构. Two elements of a binary search tree (BST) are swapped by mistake. Recov ...
 - 史上最强大的40多个纯CSS绘制的图形[转]
			
今天在国外的网站上看到了很多看似简单却又非常强大的纯CSS绘制的图形,里面有最简单的矩形.圆形和三角形,也有各种常见的多边形,甚至是阴阳太极和网站小图标,真的非常强大,分享给大家. Square(正方 ...
 - spark udf 初识初用
			
直接上代码,详见注释 import org.apache.spark.sql.hive.HiveContext import org.apache.spark.{SparkContext, Spark ...
 - window下rabbitmq环境安装
			
最近项目想用个MQ来做业务分离,看了市面上众多产品,最后选了rabbitmq,理由很简单,对window的支持很到位(其实是公司的系列产品都是.net的). 安装方法什么的就不说了,直接到官网下载双击 ...
 - PHP--------微信网页开发实现微信扫码功能
			
今天说说微商城项目中用到的扫一扫这个功能,分享一下,希望对各位有所帮助. 前提:要有公众号,和通过微信认证,绑定域名,得到相应信息,appid,appsecret等. 微信开发文档:https://m ...
 - 部分函数依赖 && 完全函数依赖
			
部分函数依赖:若x->y 并且,存在X的真子集x1,使得x1->y,则 y部分依赖于x. 完全函数依赖:若x->y并且,对于x的任何一个真子集x1,都不存在x1->y,则称y完 ...
 - 005——php字符串中的处理函数(四)
			
<?php /** * 字符串处理函数: * parse_url 解析URL.返回其组成部分 */ /* $url="http://www.lantianwang.com/admin/ ...
 - windows下使用python的scrapy爬虫框架,爬取个人博客文章内容信息
			
scrapy作为流行的python爬虫框架,简单易用,这里简单介绍如何使用该爬虫框架爬取个人博客信息.关于python的安装和scrapy的安装配置请读者自行查阅相关资料,或者也可以关注我后续的内容. ...
 - [置顶]
        VS 2017 众多重构插件
			
孙广东 2017.7.22 http://blog.csdn.NET/u010019717 1.没有任何插件的情况下: (就是Ctrl + .) 注意:这个. 要是英文的才行! 右键菜单也是 ...
 - [爬虫] 学Scrapy,顺便把它的官方教程给爬下来
			
想学爬虫主要是因为算法和数据是密切相关的,有数据之后可以玩更多有意思的事情,数据量大可以挖掘挖掘到更多的信息. 之前只会通过python中的request库来下载网页内容,再用BeautifulSou ...