欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-03-09 18:00:50
目录1、添加maven依赖2、配置application.yml3、配置动态数据源4、配置数据源操作holder5、读取自定义数据源,并配置6、动态切换关键——aop进行切换7、使用1)、配置mapp...

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)、测试结果

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

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