springboot+druid+mybatis实现多数据源访问

环境准备

  1. 创建数据库

create database test01;
create database test02;

  1. 创建表
use test01;

CREATE TABLE `person` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(255) NOT NULL COMMENT '名称',
  `age` int(3) NOT NULL COMMENT '年龄',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


use test02;

CREATE TABLE `course` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(255) NOT NULL COMMENT '课程名称',
  `teacher_name` varchar(255) NOT NULL COMMENT '老师名称',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

程序配置

  1. 引入依赖
<!-- 引入mybatis -->
<dependency>
       <groupId>org.mybatis.spring.boot</groupId>
       <artifactId>mybatis-spring-boot-starter</artifactId>
       <version>1.3.2</version>
</dependency>

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

<!-- 引入mysql -->
<dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
       <version>8.0.18</version>
</dependency>

<!-- 引入aop -->
<dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
  1. 设置application.properties中的数据源配置

spring.datasource.mysql01.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.mysql01.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.mysql01.url=jdbc:mysql://localhost:3306/test01?useUnicode=true&characterEncoding=utf8&currentSchema=public
spring.datasource.mysql01.username=root
spring.datasource.mysql01.password=123456
#config druid
#连接池的设置
#初始化时建立物理连接的个数
spring.datasource.mysql01.druid.initial-size=5
#最小连接池数量
spring.datasource.mysql01.druid.min-idle=5
#最大连接池数量 maxIdle已经不再使用
spring.datasource.mysql01.druid.max-active=20
#获取连接时最大等待时间,单位毫秒
spring.datasource.mysql01.druid.max-wait=60000
#申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
spring.datasource.mysql01.druid.test-while-idle=true
#既作为检测的间隔时间又作为testWhileIdel执行的依据
spring.datasource.mysql01.druid.time-between-eviction-runs-millis=60000
#销毁线程时检测当前连接的最后活动时间和当前时间差大于该值时,关闭当前连接
spring.datasource.mysql01.druid.min-evictable-idle-time-millis=30000
#用来检测连接是否有效的sql 必须是一个查询语句
#mysql中为 select 'x'
#oracle中为 select 1 from dual
spring.datasource.mysql01.druid.validation-query=select 'x'
#申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
spring.datasource.mysql01.druid.test-on-borrow=false
#归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
spring.datasource.mysql01.druid.test-on-return=false
#是否缓存preparedStatement,mysql5.5+建议开启
spring.datasource.mysql01.druid.pool-prepared-statements=true
#当值大于0时poolPreparedStatements会自动修改为true
spring.datasource.mysql01.druid.max-pool-prepared-statement-per-connection-size=20
#配置扩展插件
spring.datasource.mysql01.druid.filters=stat,wall
#通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.mysql01.druid.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
#合并多个DruidDataSource的监控数据
spring.datasource.mysql01.druid.use-global-data-source-stat=true
#设置访问druid监控页的账号和密码,默认没有
spring.datasource.mysql01.druid.stat-view-servlet.login-username=admin
spring.datasource.mysql01.druid.stat-view-servlet.login-password=admin



spring.datasource.mysql02.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.mysql02.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.mysql02.url=jdbc:mysql://localhost:3306/test02?useUnicode=true&characterEncoding=utf8&currentSchema=public
spring.datasource.mysql02.username=root
spring.datasource.mysql02.password=123456
#config druid
#连接池的设置
#初始化时建立物理连接的个数
spring.datasource.mysql02.druid.initial-size=5
#最小连接池数量
spring.datasource.mysql02.druid.min-idle=5
#最大连接池数量 maxIdle已经不再使用
spring.datasource.mysql02.druid.max-active=20
#获取连接时最大等待时间,单位毫秒
spring.datasource.mysql02.druid.max-wait=60000
#申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
spring.datasource.mysql02.druid.test-while-idle=true
#既作为检测的间隔时间又作为testWhileIdel执行的依据
spring.datasource.mysql02.druid.time-between-eviction-runs-millis=60000
#销毁线程时检测当前连接的最后活动时间和当前时间差大于该值时,关闭当前连接
spring.datasource.mysql02.druid.min-evictable-idle-time-millis=30000
#用来检测连接是否有效的sql 必须是一个查询语句
#mysql中为 select 'x'
#oracle中为 select 1 from dual
spring.datasource.mysql02.druid.validation-query=select 'x'
#申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
spring.datasource.mysql02.druid.test-on-borrow=false
#归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
spring.datasource.mysql02.druid.test-on-return=false
#是否缓存preparedStatement,mysql5.5+建议开启
spring.datasource.mysql02.druid.pool-prepared-statements=true
#当值大于0时poolPreparedStatements会自动修改为true
spring.datasource.mysql02.druid.max-pool-prepared-statement-per-connection-size=20
#配置扩展插件
spring.datasource.mysql02.druid.filters=stat,wall
#通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.mysql02.druid.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
#合并多个DruidDataSource的监控数据
spring.datasource.mysql02.druid.use-global-data-source-stat=true
#设置访问druid监控页的账号和密码,默认没有
spring.datasource.mysql02.druid.stat-view-servlet.login-username=admin
spring.datasource.mysql02.druid.stat-view-servlet.login-password=admin
  1. 配置mybatis
### mybatis ###
mybatis.mapper-locations=classpath:mapper/**/*.xml
mybatis.configuration.map-underscore-to-camel-case=true
  1. 创建多数据源config
    DataSource1Config
package top.cwcenter.dynamic.config;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

/**
 * @Author C.W
 * @Date 2021/1/14 上午10:17
 * @Description um MySQL配置
 */
@Configuration
public class DataSource1Config {

    @Bean(name = "dataSource1")
    @ConfigurationProperties(prefix = "spring.datasource.mysql01")
    public DataSource tidbDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "transactionManager1")
    public PlatformTransactionManager dataSourceTransactionManager(@Qualifier("dataSource1") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

}

DataSource2Config

package top.cwcenter.dynamic.config;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

/**
 * @Author C.W
 * @Date 2021/1/14 上午10:17
 * @Description um MySQL配置
 */
@Configuration
public class DataSource2Config {

    @Bean(name = "dataSource2")
    @ConfigurationProperties(prefix = "spring.datasource.mysql02")
    public DataSource tidbDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "transactionManager2")
    public PlatformTransactionManager dataSourceTransactionManager(@Qualifier("dataSource2") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

}

DataSourceContextHolder — 记录数据源,配和aop实现切换

package top.cwcenter.dynamic.config;

/**
 * ThreadLocal变量,用于记录当前线程使用的数据源,配合AOP实现数据源切换.
 *
 * @Author: C.W
 * @Date: 2019/9/29 3:15 下午
 */
public class DataSourceContextHolder {

    private static final String MYSQL_DATASOURCE = "dataSource1";
    private static final ThreadLocal<String> DATASOURCE_CONTEXT = new ThreadLocal<>();

    public static void switchDataSource(String datasource) {
        DATASOURCE_CONTEXT.set(datasource);
    }

    public static String getDataSource() {
        String dataSource = DATASOURCE_CONTEXT.get();
        if (dataSource == null || dataSource.equals("")) {
            return MYSQL_DATASOURCE;
        }
        return dataSource;
    }

    /**
     * 清除ThreadLocal的值,每次切换数据源查询后清理,避免与线程池同时使用时ThreadLocal中有残留.
     */
    public static void clear() {
        DATASOURCE_CONTEXT.remove();
    }

}

RoutingDataSource 多数据源

package top.cwcenter.dynamic.config;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 动态数据源.
 *
 * @Author: C.W
 * @Date: 2019/9/29 3:14 下午
 */
public class RoutingDataSource extends AbstractRoutingDataSource {

    /**
     * 获取当前线程的数据源.
     *
     * @return 当前使用的数据源
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }

}

DataSourceHolderConfig 数据源配置

package top.cwcenter.dynamic.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * @author: C.W
 * @date: 2020/4/13 5:44 下午
 */
@Configuration
public class DataSourceHolderConfig {

    @Autowired
    @Qualifier("dataSource1")
    private DataSource dataSource1;

    @Autowired
    @Qualifier("dataSource2")
    private DataSource dataSource2;

    @Bean(name = "routingDataSource")
    public RoutingDataSource routingDataSource() {
        Map<Object, Object> dataSourceMap = new HashMap<>(4);
        dataSourceMap.put("dataSource1", dataSource1);
        dataSourceMap.put("dataSource2", dataSource2);

        RoutingDataSource routingDataSource = new RoutingDataSource();
        routingDataSource.setTargetDataSources(dataSourceMap);
        // 默认使用um数据源.
        routingDataSource.setDefaultTargetDataSource(dataSource1);
        return routingDataSource;
    }

    @Bean(name = "mybatisConfig")
    @ConfigurationProperties(prefix = "mybatis.configuration")
    public org.apache.ibatis.session.Configuration mybatisConfig() {
        return new org.apache.ibatis.session.Configuration();
    }

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory SqlSessionFactory(@Qualifier("routingDataSource") DataSource routingDataSource, @Qualifier("mybatisConfig") org.apache.ibatis.session.Configuration configuration)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(routingDataSource);
        bean.setConfiguration(configuration);
        bean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/**/*.xml"));
        return bean.getObject();
    }
}

DataSource 注解,用于标识使用的数据源

package top.cwcenter.dynamic.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义注解,注解在dao接口或方法上,指定使用的数据源.
 *
 * @Author: Jie.He, hejie@baijiahulian.com
 * @Date: 2019/9/29 4:24 下午
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {

    String value() default "";

}

HandleDataSourceAspect aop切面,用于实际进行数据源切换操作

package top.cwcenter.dynamic.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import top.cwcenter.dynamic.config.DataSourceContextHolder;

import java.lang.reflect.Method;

/**
 * 数据源切面,作用于注解了@DataSource的方法上,取注解的name属性作为数据源.
 *
 * @Author: C.W
 * @Date: 2019/9/29 4:26 下午
 */
@Aspect
@Component
public class HandleDataSourceAspect {

    @Pointcut("@annotation(top.cwcenter.dynamic.aop.DataSource)")
    public void pointcut() {
    }

    @Before("pointcut()")
    public void beforeExecute(JoinPoint joinPoint) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        DataSource annotation = method.getAnnotation(DataSource.class);
        if (null == annotation) {
            annotation = joinPoint.getTarget().getClass().getAnnotation(DataSource.class);
        }
        if (null != annotation) {
            // 切换数据源
            DataSourceContextHolder.switchDataSource(annotation.value());
        }
    }

    @After("pointcut()")
    public void afterExecute() {
        DataSourceContextHolder.clear();
    }

}

PersonPO 实体对象

package top.cwcenter.dynamic.po;

/**
 * @Author C.W
 * @Date 2021/1/14 下午5:04
 * @Description person对象
 */
public class PersonPO {

    /**
     * id
     */
    private Long id;

    /**
     * 姓名
     */
    private String name;

    /**
     * 年龄
     */
    private Integer age;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "PersonPO{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

CoursePO实体对象

package top.cwcenter.dynamic.po;

/**
 * @Author C.W
 * @Date 2021/1/14 下午5:05
 * @Description course对象
 */
public class CoursePO {

    /**
     * id
     */
    private Long id;

    /**
     * 课程名
     */
    private String name;

    /**
     * 教师名
     */
    private String teacherName;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getTeacherName() {
        return teacherName;
    }

    public void setTeacherName(String teacherName) {
        this.teacherName = teacherName;
    }

    @Override
    public String toString() {
        return "CoursePO{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", teacherName='" + teacherName + '\'' +
                '}';
    }
}

PersonDao 数据库操作层—使用默认数据源

package top.cwcenter.dynamic.dao;

import org.apache.ibatis.annotations.Mapper;
import top.cwcenter.dynamic.po.PersonPO;

import java.util.List;

/**
 * @Author C.W
 * @Date 2021/1/14 下午5:06
 * @Description person操作
 */
@Mapper
public interface PersonDao {

    /**
     * 插入
     *
     * @param personPO
     * @return
     */
    Integer insert(PersonPO personPO);

    /**
     * 查询所有
     *
     * @return
     */
    List<PersonPO> selectAll();

}

CourseDao 数据库操作层—使用02数据源

package top.cwcenter.dynamic.dao;

import org.apache.ibatis.annotations.Mapper;
import top.cwcenter.dynamic.aop.DataSource;
import top.cwcenter.dynamic.po.CoursePO;

import java.util.List;

/**
 * @Author C.W
 * @Date 2021/1/14 下午5:06
 * @Description course操作
 */
@Mapper
public interface CourseDao {

    /**
     * 插入
     *
     * @param coursePO
     * @return
     */
    @DataSource("dataSource2")
    Integer insert(CoursePO coursePO);

    /**
     * 查询所有
     *
     * @return
     */
    @DataSource("dataSource2")
    List<CoursePO> selectAll();

}

  1. 配置mapper
    CourseDao.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="top.cwcenter.dynamic.dao.CourseDao">

    <insert id="insert" parameterType="top.cwcenter.dynamic.po.CoursePO">
        insert into course (name, teacher_name)
        values (#{name}, #{teacherName})
    </insert>

    <select id="selectAll" resultType="top.cwcenter.dynamic.po.CoursePO">
        select *
        from course
    </select>

</mapper>

PersonDao.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="top.cwcenter.dynamic.dao.PersonDao">

    <insert id="insert" parameterType="top.cwcenter.dynamic.po.PersonPO">
        insert into person (name, age)
        values (#{name}, #{age})
    </insert>

    <select id="selectAll" resultType="top.cwcenter.dynamic.po.PersonPO">
        select *
        from person
    </select>

</mapper>
  1. 测试使用
    MybatisTests
package top.cwcenter.dynamic;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import top.cwcenter.dynamic.dao.CourseDao;
import top.cwcenter.dynamic.dao.PersonDao;
import top.cwcenter.dynamic.po.CoursePO;
import top.cwcenter.dynamic.po.PersonPO;

import java.util.List;

/**
 * @Author C.W
 * @Date 2021/1/14 下午5:11
 * @Description mybatis测试
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class MybatisTests {

    @Autowired
    private PersonDao personDao;

    @Autowired
    private CourseDao courseDao;


    @Test
    public void test() {
        // 查询两个数据源数据
        List<PersonPO> personPOList = personDao.selectAll();
        List<CoursePO> coursePOList = courseDao.selectAll();
        System.out.println(String.format("数据源1 数据条数:%d, 数据:%s", personPOList.size(), personPOList));
        System.out.println(String.format("数据源2 数据条数:%d, 数据:%s", coursePOList.size(), coursePOList));
        // 新增数据
        PersonPO personPO = new PersonPO();
        personPO.setName("小明");
        personPO.setAge(25);
        personDao.insert(personPO);
        CoursePO coursePO = new CoursePO();
        coursePO.setName("java程序设计");
        coursePO.setTeacherName("王老师");
        courseDao.insert(coursePO);

        personPOList = personDao.selectAll();
        coursePOList = courseDao.selectAll();
        System.out.println(String.format("数据源1 数据条数:%d, 数据:%s", personPOList.size(), personPOList));
        System.out.println(String.format("数据源2 数据条数:%d, 数据:%s", coursePOList.size(), coursePOList));
    }

}

输出结果:

2021-01-14 17:15:22.754  INFO 12674 --- [           main] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} inited
2021-01-14 17:15:24.528  INFO 12674 --- [           main] com.alibaba.druid.pool.DruidDataSource   : {dataSource-2} inited
数据源1 数据条数:0, 数据:[]
数据源2 数据条数:0, 数据:[]
数据源1 数据条数:1, 数据:[PersonPO{id=1, name='小明', age=25}]
数据源2 数据条数:1, 数据:[CoursePO{id=1, name='java程序设计', teacherName='王老师'}]
2021-01-14 17:15:25.233  INFO 12674 --- [       Thread-2] com.alibaba.druid.pool.DruidDataSource   : {dataSource-2} closed
2021-01-14 17:15:25.234  INFO 12674 --- [       Thread-2] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} closed

demo地址

https://github.com/ChengChengWei/blog-demo/tree/main/dynamic-datasource

小短腿的个人公众号上线啦,后续技术分享将优先在公众号进行哈,大家可以关注公众号,小短腿有什么新的分享会及时同步哈
在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值