SpringBoot 自定义+动态切换数据源教程

目录
  • 1、添加maven依赖
  • 2、配置application.yml
  • 3、配置动态数据源
  • 4、配置数据源操作Holder
  • 5、读取自定义数据源,并配置
  • 6、动态切换关键——AOP进行切换
  • 7、使用
    • 1)、配置mapper
    • 2)、配置service
    • 3)、单元测试调用
    • 4)、测试结果

1、添加maven依赖

<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.3</version>
        </dependency>
        <!--properties动态注入-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <!--springBoot的aop-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

2、配置application.yml

# 数据库访问配置
# 主数据源,默认的
druid:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://192.168.1.113:3306/test?useUnicode=true&characterEncoding=utf-8
    username: root
    password: root
    # 下面为连接池的补充设置,应用到上面所有数据源中
    # 初始化大小,最小,最大
    initialSize: 5
    minIdle: 5
    maxActive: 20
    # 配置获取连接等待超时的时间
    maxWait: 60000
    # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
    timeBetweenEvictionRunsMillis: 60000
    # 配置一个连接在池中最小生存的时间,单位是毫秒
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    # 打开PSCache,并且指定每个连接上PSCache的大小
    poolPreparedStatements: true
    maxPoolPreparedStatementPerConnectionSize: 20
    # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
    filters: stat,wall,log4j
    # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
    connectionProperties:
      druid:
        stat:
          mergeSql: true
          slowSqlMillis: 5000
    # 合并多个DruidDataSource的监控数据
#多数据源
mysql-db:
  datasource:
    names: logic,dao
    logic:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://192.168.1.113:3306/test1?useUnicode=true&characterEncoding=utf-8
      username: root
      password: root
    dao:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://192.168.1.113:3306/test2?useUnicode=true&characterEncoding=utf-8
      username: root
      password: root

3、配置动态数据源

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
 * 动态数据源
 * @author 陈梓平
 * @date 2017/10/9.
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceHolder.getDataSource();
    }
}

4、配置数据源操作Holder

import java.util.ArrayList;
import java.util.List;
/**
 * 数据源操作
 * @author 陈梓平
 * @date 2017/10/9.
 */
public class DataSourceHolder {
    //线程本地环境
    private static final ThreadLocal<String> contextHolders = new ThreadLocal<String>();
    //数据源列表
    public static List<String> dataSourceIds = new ArrayList<>();
    //设置数据源
    public static void setDataSource(String customerType) {
        contextHolders.set(customerType);
    }
    //获取数据源
    public static String getDataSource() {
        return (String) contextHolders.get();
    }
    //清除数据源
    public static void clearDataSource() {
        contextHolders.remove();
    }
    /**
     * 判断指定DataSrouce当前是否存在
     * @param dataSourceId
     * @return
     * @author SHANHY
     * @create  2016年1月24日
     */
    public static boolean containsDataSource(String dataSourceId){
        return dataSourceIds.contains(dataSourceId);
    }
}

5、读取自定义数据源,并配置

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValues;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.bind.RelaxedDataBinder;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
 * 数据源配置
 * @author 陈梓平
 * @date 2017/10/9.
 */
@Component
@Configuration
public class DynamicDataSourceConfig implements EnvironmentAware {
    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceConfig.class);
    // 默认数据源
    private DataSource defaultDataSource;
    // 属性值
    private PropertyValues dataSourcePropertyValues;
    // 如配置文件中未指定数据源类型,使用该默认值
    private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";
    private ConversionService conversionService = new DefaultConversionService();
    private Map<String, DataSource> customDataSources = new HashMap<>();
    @Override
    public void setEnvironment(Environment environment) {
        initDefaultDatasource(environment);
        initOtherDatasource(environment);
    }
    private void initOtherDatasource(Environment environment) {
        RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(environment, "mysql-db.datasource.");
        String dsPrefixs = propertyResolver.getProperty("names");
        for (String dsPrefix : dsPrefixs.split(",")) {// 多个数据源
            Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix+".");
            DataSource ds = buildDataSource(dsMap);
            customDataSources.put(dsPrefix, ds);
            dataBinder(ds, environment);
        }
    }
    private void initDefaultDatasource(Environment environment) {
        // 读取主数据源
        RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(environment, "druid.datasource.");
        Map<String, Object> dsMap = new HashMap<>();
        dsMap.put("type", propertyResolver.getProperty("type"));
        dsMap.put("driver-class-name", propertyResolver.getProperty("driver-class-name"));
        dsMap.put("url", propertyResolver.getProperty("url"));
        dsMap.put("username", propertyResolver.getProperty("username"));
        dsMap.put("password", propertyResolver.getProperty("password"));
        defaultDataSource = buildDataSource(dsMap);
        DataSourceHolder.dataSourceIds.add("ds1");
        dataBinder(defaultDataSource, environment);
    }
    /**
     * 创建DataSource
     * @param dsMap
     * @return
     * @author SHANHY
     * @create 2016年1月24日
     */
    @SuppressWarnings("unchecked")
    public DataSource buildDataSource(Map<String, Object> dsMap) {
        try {
            Object type = dsMap.get("type");
            if (type == null)
                type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource
            Class<? extends DataSource> dataSourceType;
            dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
            String driverClassName = dsMap.get("driver-class-name").toString();
            String url = dsMap.get("url").toString();
            String username = dsMap.get("username").toString();
            String password = dsMap.get("password").toString();
            DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url)
                    .username(username).password(password).type(dataSourceType);
            return factory.build();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 为DataSource绑定更多数据
     * @param dataSource
     * @param env
     * @author SHANHY
     * @create  2016年1月25日
     */
    private void dataBinder(DataSource dataSource, Environment env){
        RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
        //dataBinder.setValidator(new LocalValidatorFactory().run(this.applicationContext));
        dataBinder.setConversionService(conversionService);
        dataBinder.setIgnoreNestedProperties(false);//false
        dataBinder.setIgnoreInvalidFields(false);//false
        dataBinder.setIgnoreUnknownFields(true);//true
        if(dataSourcePropertyValues == null){
            Map<String, Object> rpr = new RelaxedPropertyResolver(env, "druid.datasource.").getSubProperties(".");
            Map<String, Object> values = new HashMap<>(rpr);
            // 排除已经设置的属性
            values.remove("type");
            values.remove("driver-class-name");
            values.remove("url");
            values.remove("username");
            values.remove("password");
            dataSourcePropertyValues = new MutablePropertyValues(values);
        }
        dataBinder.bind(dataSourcePropertyValues);
    }
    @Bean(name = "dataSource")
    public DynamicDataSource dataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 默认数据源
        dynamicDataSource.setDefaultTargetDataSource(defaultDataSource);
        // 配置多数据源
        Map<Object, Object> dsMap = new HashMap(5);
        dsMap.put("ds1", defaultDataSource);
        dsMap.putAll(customDataSources);
        for (String key : customDataSources.keySet())
            DataSourceHolder.dataSourceIds.add(key);
        dynamicDataSource.setTargetDataSources(dsMap);
        return dynamicDataSource;
    }
}

6、动态切换关键——AOP进行切换

/**
 * 动态数据源注解
 * @author 陈梓平
 * @date 2017/10/9.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({
        ElementType.METHOD
})
public @interface DS {
    String name() default "ds1";
}
import com.chen.config.dynamicDS.DataSourceHolder;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
 * 设置数据源切面
 * @author 陈梓平
 * @date 2017/10/9.
 */
@Aspect
@Order(-1)// 保证该AOP在@Transactional之前执行
@Component
public class DynamicDataSourceAspect {
    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
    @Before("@annotation(ds)")
    public void changeDataSource(JoinPoint point, DS ds) throws Throwable {
        String dsId = ds.name();
        if (!DataSourceHolder.containsDataSource(dsId)) {
            logger.error("数据源[{}]不存在,使用默认数据源 > {}", ds.name(), point.getSignature());
        } else {
            logger.debug("Use DataSource : {} > {}", ds.name(), point.getSignature());
            DataSourceHolder.setDataSource(ds.name());
        }
    }
    @After("@annotation(ds)")
    public void restoreDataSource(JoinPoint point, DS ds) {
        logger.debug("Revert DataSource : {} > {}", ds.name(), point.getSignature());
        DataSourceHolder.clearDataSource();
    }
}

7、使用

1)、配置mapper

/**
 * @author 陈梓平
 * @date 2017/10/9.
 */
public interface DynamicDSMapper {
    Integer queryJournal();
    String queryUser();
    String queryType();
}
<?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="com.chen.mapper.DynamicDSMapper">
    <select id="queryJournal" resultType="java.lang.Integer">
        SELECT uid FROM journal
    </select>
    <select id="queryUser" resultType="java.lang.String">
        SELECT name FROM user
    </select>
    <select id="queryType" resultType="java.lang.String">
        SELECT parent FROM p_type
    </select>
</mapper>

2)、配置service

/**
 * @author 陈梓平
 * @date 2017/10/9.
 */
@Service
public class DynamicServciceImpl implements DynamicServcice {
    @Autowired
    private DynamicDSMapper dynamicDSMapper;
    @DS()
    public Integer ds1() {
        return dynamicDSMapper.queryJournal();
    }
    @DS(name = "logic")
    public String ds2() {
        return dynamicDSMapper.queryUser();
    }
    @DS(name = "dao")
    public String ds3() {
        return dynamicDSMapper.queryType();
    }
}

3)、单元测试调用

/**
 * 多数原测试
 * @author 陈梓平
 * @date 2017/10/9.
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestDynamicDS {
    private Logger logger = LoggerFactory.getLogger(TestDynamicDS.class);
//
    @Autowired
    private DynamicServcice dynamicServcice;
    @Test
    public void test() {
//        Integer integer = dynamicServcice.ds1();
//        logger.info("integer:"+integer);
//        String ds2 = dynamicServcice.ds2();
//        logger.info("ds2:"+ds2);
        String ds3 = dynamicServcice.ds3();
        logger.info("ds3:"+ds3);
    }
}

4)、测试结果

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Spring Boot 动态数据源示例(多数据源自动切换)

    本文实现案例场景: 某系统除了需要从自己的主要数据库上读取和管理数据外,还有一部分业务涉及到其他多个数据库,要求可以在任何方法上可以灵活指定具体要操作的数据库. 为了在开发中以最简单的方法使用,本文基于注解和AOP的方法实现,在spring boot框架的项目中,添加本文实现的代码类后,只需要配置好数据源就可以直接通过注解使用,简单方便. 一配置二使用 1. 启动类注册动态数据源 2. 配置文件中配置多个数据源 3. 在需要的方法上使用注解指定数据源 1.在启动类添加 @Import({Dyna

  • spring boot + mybatis实现动态切换数据源实例代码

    前言 前几天有个需求,需要使用不同的数据源,例如某业务要用A数据源,另一个业务要用B数据源.我上网收集了一些资料整合了一下,虽然最后这个需求不了了之了,但是多数据源动态切换还是蛮好用的,所以记录一下,或许以后有用呢?或者自己感兴趣又想玩呢! 下面话不多说了,随着小编来一起看看详细的介绍吧 方法如下: 1.加个依赖 <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybat

  • spring boot多数据源动态切换代码实例

    这篇文章主要介绍了spring boot多数据源动态切换代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 当项目中存在多数据源时,就涉及到数据源的动态切换,通过研究,特此记录一下. 1.maven依赖 <!--数据库连接--> <dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc6</artifactId> &

  • SpringBoot数据访问自定义使用Druid数据源的方法

    数据访问之Druid数据源的使用 说明:该数据源Druid,使用自定义方式实现,后面文章使用start启动器实现,学习思路为主. 为什么要使用数据源: ​数据源是提高数据库连接性能的常规手段,数据源会负责维持一个数据连接池,当程序创建数据源实例时,系统会一次性地创建多个数据库连接,并把这些数据库连接保存在连接池中. ​当程序需要进行数据库访问时,无须重新获得数据库连接,而是从连接池中取出一个空闲的数据库连接. ​当程序使用数据库连接访问数据库结束后,无须关闭数据库连接,而是将数据库连接归还给连接

  • SpringBoot 自定义+动态切换数据源教程

    目录 1.添加maven依赖 2.配置application.yml 3.配置动态数据源 4.配置数据源操作Holder 5.读取自定义数据源,并配置 6.动态切换关键--AOP进行切换 7.使用 1).配置mapper 2).配置service 3).单元测试调用 4).测试结果 1.添加maven依赖 <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</ar

  • 详细聊聊SpringBoot中动态切换数据源的方法

    其实这个表示有点不太对,应该是 Druid 动态切换数据源的方法,只是应用在了 springboot 框架中,准备代码准备了半天,之前在一次数据库迁移中使用了,发现 Druid 还是很强大的,用来做动态数据源切换很方便. 首先这里的场景跟我原来用的有点点区别,在项目中使用的是通过配置中心控制数据源切换,统一切换,而这里的例子多加了个可以根据接口注解配置 第一部分是最核心的,如何基于 Spring JDBC 和 Druid 来实现数据源切换,是继承了org.springframework.jdbc

  • SpringBoot多数据源配置并通过注解实现动态切换数据源

    目录 1. 环境准备 1.1 数据库准备 1.2 项目创建 2. ThreadLocal类介绍 3. AbstractRoutingDataSource类介绍 4. 具体实现 4.1 定义数据源枚举类 4.2 创建动态多数据源类 4.3 创建动态多数据源配置类 4.4 自定义注解用于指定数据源 4.5 AOP实现动态切换数据源 5. 测试使用 5.1 配置数据源 5.2 创建实体类 5.3 服务层代码 5.4 控制层代码 1. 环境准备 1.1 数据库准备 一个本地环境的MySQL数据库,数据库

  • Springboot动态切换数据源的具体实现与原理分析

    目录 前言 具体实现: 原理分析: 总结 前言 在springboot项目中只需一句代码即可实现多个数据源之间的切换: // 切换sqlserver数据源: DataSourceContextHolder.setDataBaseType(DataSourceEnum.SQLSERVER_DATASOURCE); ...... // 切换mysql数据源 DataSourceContextHolder.setDataBaseType(DataSourceEnum.MYSQL_DATASOURCE)

  • Spring+Mybatis动态切换数据源的方法

    功能需求是公司要做一个大的运营平台: 1.运营平台有自身的数据库,维护用户.角色.菜单.部分以及权限等基本功能. 2.运营平台还需要提供其他不同服务(服务A,服务B)的后台运营,服务A.服务B的数据库是独立的. 所以,运营平台至少要连三个库:运营库,A库,B库,并且希望达到针对每个功能请求能够自动切换到对应的数据源(我最终实现是针对Service的方法级别进行切换的,也可以实现针对每个DAO层的方法进行切换.我们系统的功能是相互之间比较独立的). 第一步:配置多数据源 1.定义数据源: 我采用的

  • Spring + Mybatis 项目实现动态切换数据源实例详解

    项目背景:项目开发中数据库使用了读写分离,所有查询语句走从库,除此之外走主库. 最简单的办法其实就是建两个包,把之前数据源那一套配置copy一份,指向另外的包,但是这样扩展很有限,所有采用下面的办法. 参考了两篇文章如下: http://www.jb51.net/article/111840.htm http://www.jb51.net/article/111842.htm 这两篇文章都对原理进行了分析,下面只写自己的实现过程其他不再叙述. 实现思路是: 第一步,实现动态切换数据源:配置两个D

  • spring boot动态切换数据源的实现

    当数据量比较大的时候,我们就需要考虑读写分离了,也就是动态切换数据库连接,对指定的数据库进行操作.在spring中实现动态的切换无非就是利用AOP实现.我们可以使用mybatis-plus作者开发的插件dynamic-datasource-spring-boot-starter. demo地址:https://github.com/songshijun1995/spring-boot-dynamic-demo 新建项目引入依赖 <dependency> <groupId>com.b

  • spring-data-redis 动态切换数据源的方法

    最近遇到了一个麻烦的需求,我们需要一个微服务应用同时访问两个不同的 Redis 集群.一般我们不会这么使用 Redis,但是这两个 Redis 本来是不同业务集群,现在需要一个微服务同时访问. 其实我们在实际业务开发的时候,可能还会遇到类似的场景.例如 Redis 读写分离,这个也是 spring-data-redis 没有提供的功能,底层连接池例如 Lettuce 或者 Jedis 都提供了获取只读连接的 API,但是缺陷有两个: 上层 spring-data-redis 并没有封装这种接口

  • mybatis-plus @DS实现动态切换数据源原理

    1.mybatis-plus @DS实现动态切换数据源原理 首先mybatis-plus使用com.baomidou.dynamic.datasource.AbstractRoutingDataSource继承 AbstractDataSource接管数据源:具体实现类为com.baomidou.dynamic.datasource.DynamicRoutingDataSource.项目初始化调用public synchronized void addDataSource(String ds,

随机推荐