springboot+druid+mybatis 多数据源切换(带源码+sql)

业务场景

互联网项目数据量大,很多业务模块使用统一数据库,会导致DB 压力过大,不足以支撑服务要求,于是现如今分布式项目下大都使用多个数据库,项目中的多数据源切换成为了必要的技术支持.

项目github 地址

https://github.com/MinJW/dynamic-db-demo:

本文中使用的所有代码 都在项目中有 包括压力测试和sql提供

项目架构

JDK1.8
Spring-boot 版本:1.5.9.RELEASE
MYSQL57
全部依赖:

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.1</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.6</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

数据

在这里插入图片描述
一共四个库 分四个模块
在这里插入图片描述
每个数据库都是一张表 一条数据 id都是1 单name为模块名 用来测试数据源切换是否成功

配置文件

# 用户模块
com.mjw.datasource.user.url=jdbc:mysql://localhost:3306/mjw_user
com.mjw.datasource.user.driver-class-name=com.mysql.jdbc.Driver
com.mjw.datasource.user.username=root
com.mjw.datasource.user.password=root

# 钱包模块
com.mjw.datasource.wallet.url=jdbc:mysql://localhost:3306/mjw_wallet
com.mjw.datasource.wallet.driver-class-name=com.mysql.jdbc.Driver
com.mjw.datasource.wallet.username=root
com.mjw.datasource.wallet.password=root

# 设备模块
com.mjw.datasource.device.url=jdbc:mysql://localhost:3306/mjw_device
com.mjw.datasource.device.driver-class-name=com.mysql.jdbc.Driver
com.mjw.datasource.device.username=root
com.mjw.datasource.device.password=root

# 订单模块
com.mjw.datasource.order.url=jdbc:mysql://localhost:3306/mjw_order
com.mjw.datasource.order.driver-class-name=com.mysql.jdbc.Driver
com.mjw.datasource.order.username=root
com.mjw.datasource.order.password=root

核心代码

我使用的方式大致讲 是在service层加注解标识使用哪个数据源,然后service层会使用aop 获取注解上的数据源标识,从而切换到响应数据源上.

数据源配置

@Configuration
public class DataSourceConfigurer {

    private Logger logger = LoggerFactory.getLogger(DataSourceConfigurer.class);

    @Bean(name = "user")
    @Primary
    @ConfigurationProperties(prefix = "com.mjw.datasource.user")
    public DataSource userDataSource(){
        logger.debug("userDataSource init ...");
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "wallet")
    @ConfigurationProperties(prefix = "com.mjw.datasource.wallet")
    public DataSource walletDataSource(){
        logger.debug("walletDataSource init ...");
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "device")
    @ConfigurationProperties(prefix = "com.mjw.datasource.device")
    public DataSource deviceDataSource(){
        logger.debug("deviceDataSource init ...");
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "order")
    @ConfigurationProperties(prefix = "com.mjw.datasource.order")
    public DataSource orderDataSource(){
        logger.debug("orderDataSource init ...");
        return DruidDataSourceBuilder.create().build();
    }


    /**
     * @Title
     * @Description 动态数据初始化
     * @param
     * @return javax.sql.DataSource
     * @throw
     * @author MinJunWen
     * @date 2018/11/17 16:25
     */
    @Bean
    public DataSource dynamicDataSource(){
        MyDynamicDataSource myDynamicDataSource = new MyDynamicDataSource();


        Map<Object,Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put("user",userDataSource());
        dataSourceMap.put("wallet",walletDataSource());
        dataSourceMap.put("device",deviceDataSource());
        dataSourceMap.put("order",orderDataSource());

        //设置默认的dataSource
        myDynamicDataSource.setDefaultTargetDataSource(userDataSource());

        //设置数据源map
        myDynamicDataSource.setTargetDataSources(dataSourceMap);

        return myDynamicDataSource;
    }

    @Bean
    @ConfigurationProperties(prefix = "mybatis")
    public SqlSessionFactoryBean sqlSessionFactoryBean() {
        logger.debug("sqlSessionFactoryBean init ...");
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDataSource());
        return sqlSessionFactoryBean;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        logger.debug("transactionManager init ...");
        return new DataSourceTransactionManager(dynamicDataSource());
    }
}

注意: 一定要有一个默认数据源 如果未设置数据源或者设置的数据源找不到会使用默认数据源,我这里默认是userDataSource

切换数据源核心

public class MyDynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DbLookupKeyContextHolder.getDataSourceKey();
    }

}

切换数据源核心就是 继承 AbstractRoutingDataSource 类重写determineCurrentLookupKey 方法,返回dataSource Bean标识.

DbLookupKeyContextHolder 实现

public class DbLookupKeyContextHolder {

    /**
     * @Description 存放当前线程线程 选择数据链接 key
     * @author MinJunWen
     * @date 2018/11/23 14:42
     */
    private static final ThreadLocal<String> CURRENT_LOOKUP_KEY = new ThreadLocal<>();

    /**
     * @Description 设置当前选择数据库的key
     * @author MinJunWen
     * @date 2018/11/23 14:42
     */
    public static void setDataSourceKey(String lookupKey){
        CURRENT_LOOKUP_KEY.set(lookupKey);
    }

    /**
     * @Description 获取当前选择数据库的key
     * @author MinJunWen
     * @date 2018/11/23 15:34
     */
    public static String getDataSourceKey(){
        return CURRENT_LOOKUP_KEY.get();
    }

}

每个线程中存储使用的数据源标识,会在aop且面中设置数据源

aop切面

@Aspect
@Component
public class ChangeDataSourceAop {

    private Logger logger = LoggerFactory.getLogger(ChangeDataSourceAop.class);

    /**
     * @Description 切面 针对所有service
     * @author MinJunWen
     * @date 2018/11/23 15:23
     */
    @Pointcut("execution( * com.mjw.business.*.service.*.*(..))")
    public void serviceCut(){};

    @Before("serviceCut()")
    public void changeDataSource(JoinPoint point) {
        String value = point.getTarget().getClass().getAnnotation(MjwDb.class).value().getValue();
        if(!StringUtils.isNullOrEmpty(value)){
            logger.info("switch db lookupkey ==>" + value);
            DbLookupKeyContextHolder.setDataSourceKey(value);
        }
    }

}

我这里切的是所有service调用
从service实例上获取我自定义的注解标识 来获取对应的数据源 设置到当前线程中即可

注意: 我是从service层切面来设置数据源 所以在一个service 要获取另一个库的数据 不能直接注入dao来调用 而是应该注入service来使用.否则将无法正确切换数据源,开发时要注意代码规范.

注解

@Target({  ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface MjwDb {

    MjwDbEnum value();
}

注解的value 强制使用枚举

数据源标识枚举

public enum MjwDbEnum {

    DEVICE("device"),ORDER("order"),USER("user"),WALLET("wallet");

    private String value;
    MjwDbEnum(String value){
        this.value = value;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

测试运行

具体的controller service dao 就不方这里了 有源码提供自己可以测试下
每个模块提供一个根据id查询的接口
在这里插入图片描述
/user 使用的userService 是有user数据源标识的 根据我们的数据 只有user数据库才会有返沪user 切换时正确得
根据我aop中打印的日志 也是没问题的

2019-11-09 14:58:41.934  INFO 500356 --- [nio-8888-exec-2] com.mjw.common.aop.ChangeDataSourceAop   : switch db lookupkey ==>user

压力测试

其实它是存储在当前请求的线程变量中的,不用担心会多个请求中使用的数据源混乱问题

这里是一个简单的js请求 模仿下来测试

<script type="text/javascript">
	var log = console.log.bind(console)
	var fields = ['device','order','user','wallet']

	var intv = setInterval("func()","1");
	var func = function(){
	    for(var i = 0; i < 1000; i++){
            parseQuest(fields[i % 4])
			//log(fields[i % 4])
		}
	}

	$(function(){
		$(document).keydown(function(event){
	    if(event.keyCode == 32){
				clearInterval(intv)
	    }
	  });
	})

	var parseQuest = function(field){
		$.ajax({
            url:'http://localhost:8888/'+field,
            data:{'id':1},
			type:"post",
			dataType:"json",
			success:function(data){
                if(field == data.name){
                    //log('success==>' + data.name)
				}else{
                    log(field+'=>success**********'+data.name)
				}
			},
			fail:function(data){
				log(field+'=>error***************'+data)
			}
		})
	}
	
</script>

每秒跑1000个请求 每个数据源有250个请求 ,无任何压力 运行正常

代码地址

源码地址:https://github.com/MinJW/dynamic-db-demo

测试的js代码在 resources/static/job中 直接在文件夹中打开就行 不用跑起项目访问

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值