SpringBoot、Mybatis 动态多数据源分享

GitChat学术分享    网址:https://gitbook.cn/books/5c6ce6d0565cd4570835e62c/index.html

SpringBoot、Mybatis 动态多数据源分享_第1张图片

随着业务的发展,数据库的压力肯定会越来越大,如何分割数据库的读写压力是我们需要考虑的问题,而能够动态的切换数据源就是我们的首要目标。

在网上搜索 MyBatis 多数据源大部分文章都是配置到配置文件里。这样每次新建数据源都需要由 Java 开发者自己修改配置文件,修改相关的配置类,工具类,然后再重启后台服务才能生效,一旦后续增加的数据源较多,新增数据源将会是个繁琐的重复性工作。

经过一段时间总结和借鉴多数据源技术文章,终于构建了一套符合需求的项目,相对之前的配置数据源配置文件大大减少了 Java 开发者的工作量,也降低了由于修改代码产生的出错率。因此借此机会在 GitChat 平台和大家分享。

项目优点

  • 多数据源 支持主流数据源 MySQL、Sqlserver、Oracle 等数据源

  • 动态切换 新建数据源,只需在默认数据库增加一行数据源配置信息,不修改配置文件。 新数据源参数传递到后台服务后,判断是否存在该数据源,若不存在自动建立新的连接池。

  • 不重启服务 由于不需要修改配置文件,后台服务就不需要重启,提高了后台服务的稳定性和可靠性。

  • 持久化数据源 超过 20 分钟无服务请求,再次请求不会出现超时或数据库重新连接错误提示。 服务所在服务器断网一段时间后,又再次联网,再次请求服务不会提示数据库连接断开等错误提示。

  • 可扩展性强 支持单数据源和多数据源,对于未来项目扩展其他数据源对接提供了基础配置。

  • 线程独立 每个请求独立占用一个单独线程,不会出现数据混乱。同时可以在方法中自由的切换数据源,实现多库数据汇总功能。

适用环境

  • 单数据源项目、多数据源项目。

  • 系统新旧框架切换时数据源切换。

  • 项目周期数据源配置 :开发库、测试库、正式库配置。

原理介绍

Filter 拦截器 和 AbstractRoutingDataSource 抽象类

Filter 拦截器

直接实现 Filter 接口,重写拦截方法,再到 @WebFilter 注解上配置拦截规则即可实现。

下面是拦截到请求中的 dsn 参数,重写拦截方法,判断是否存在数据源,若不存在数据源则新建数据源并切换到该数据源,再继续下一个链。

    @WebFilter(urlPatterns = "/*")
    public class DataSourceFilter implements Filter {

    private static final Logger log = LoggerFactory.getLogger(DataSourceFilter.class);

    @Resource
    private ProjectListMapper projectListMapper;

    @Override
    public void destroy() {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain arg2) throws IOException, ServletException {
        String dsn=request.getParameter("dsn");//SqlServer_ip_数据库

        try {

            if (dsn != null && dsn.trim().length() 0) {
                //根据dsn获取连接信息
                SysDBInfo sysDBInfo = new SysDBInfo();
                ProjectList projectList=new ProjectList();
                projectList.setDsn(dsn);
                //切换到默认数据库
                                 DataSourceContextHolder.setDataSourceType(DEFAULT_DATASOURCE_TYPE);
                //根据dsn获取数据库配置
                List0){
                    sysDBInfo.setDbType(dataMapList.get(0).getDataType());
                    sysDBInfo.setIpAddress(dataMapList.get(0).getAddress());
                    sysDBInfo.setServiceName(dataMapList.get(0).getData());
                    sysDBInfo.setUserName(dataMapList.get(0).getUserName());
                    sysDBInfo.setPwd(dataMapList.get(0).getPwd());
                    sysDBInfo.setDsn(dataMapList.get(0).getDsn());
                }

                //如果不存在当前数据源,创建数据源并添加
                if (!DataSourceConfig.hasCurrentDataSource(sysDBInfo)) {
                    log.info("不存在数据源:{}",sysDBInfo);
                    DataSource ds = DataSourceConfig.buildDataSource(sysDBInfo);
                    DataSourceConfig.addDataSource();
                }else {
                    log.info("存在数据源:{}",sysDBInfo);
                }
                //使用当前数据源
                DataSourceContextHolder.setDataSourceType(sysDBInfo.getDsn());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        arg2.doFilter(request, response); //到下一个链

        //清除数据源
        DataSourceContextHolder.clearDataSourceType();
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {

    }

    }

AbstractRoutingDataSource

这个类是实现多数据源的关键,他的作用就是动态切换数据源,实质:有多少个数据源就存多少个数据源在 targetDataSources( 是 AbstractRoutingDataSource 的一个 map 类型的属性,其中 value 为每个数据源,key 表示每个数据源的名字)这个属性中,然后根据 determineCurrentLookupKey()这个方法获取当前数据源在 map 中的key值,然后 determineTargetDataSource()方法中动态获取当前数据源,如果当前数据源不存并且默认数据源也不存在就抛出异常。

具体实现

定义一个动态数据源

继承 AbstractRoutingDataSource 抽象类,并重写determineCurrentLookupKey()方法

  public class DynamicDataSource extends AbstractRoutingDataSource{

 @Override  protected Object determineCurrentLookupKey() {   return
        DataSourceContextHolder.getDataSourceType();
    }
 }

创建一个切换数据源类型的类

ThreadLocal 就是为了线程的安全性,每个线程之间不会相互影响。

public class DataSourceContextHolder {    public static final Logger
log = LoggerFactory.getLogger(DataSourceContextHolder.class);
       /**
    * 默认数据源
    */   
     public static final String DEFAULT_DATASOURCE_TYPE = DataSourceConfig.DEFAULT_DATASOURCE_TYPE;
       private static final ThreadLocal();
public static void setDataSourceType(String dataSourceType) {
      log.info("切换到{}数据源",dataSourceType);
      contextHolder.set(dataSourceType);    }
       //获取数据源    
       public static String getDataSourceType() {
      if(contextHolder.get() == null) {
         return DEFAULT_DATASOURCE_TYPE;
      }

 return contextHolder.get();    }
      //清除数据源    
       public static void clearDataSourceType() {
      contextHolder.remove();    } }

定义默认数据源

通过引入配置文件,调用 DataSourceBuilder.create().build() 创建数据源;然后将默认数据源信息设置到 DataSource 里。

 

创建动态数据源

创建方法 buildDataSource()将数据源信息动态添加 map 里,再将 map 导入到 DataSource 里。

将方法 DataSource dataSource = factory.build(); 替换为 BasicDataSource dataSource1= (BasicDataSource) factory.build(); 这样可以给数据源对象增加持久化参数等。

启动类修改

将 @SpringBootApplication 改为 @SpringBootApplication(exclude= {DataSourceAutoConfiguration.class}) //不自动配置 DataSource 。 项目启动后,调用查询默认数据源的数据源列表,将数据源列表插入到 DataSoure 中。

项目结构

运行日志

项目启动数据源截图

项目启动日志

2019/02/25-23:46:37 [main] INFO  com.d_cloud.Application-
<<============= Spring Boot Start OK =============> 
2019/02/25-23:46:39 [main] DEBUG
com.d_cloud.dao.sqlserver.ProjectListMapper.selectAllBeans- ==
Preparing: select
pj_seq,pj_name,dsn,data,url,dataType,address,userName,pwd from
PROJECTLIST where userName is not null order by dsn,pj_name   
2019/02/25-23:46:39 [main] DEBUG
com.d_cloud.dao.sqlserver.ProjectListMapper.selectAllBeans- ==>
Parameters:    2019/02/25-23:46:39 [main] DEBUG
com.d_cloud.dao.sqlserver.ProjectListMapper.selectAllBeans- <==     
Total: 3   2019/02/25-23:46:43 [main] INFO 
com.d_cloud.common.datasource.DataSourceConfig- 添加H191_dev数据源到Map  
2019/02/25-23:46:44 [main] INFO 
com.d_cloud.common.datasource.DataSourceConfig- 添加H191_test数据源到Map  
2019/02/25-23:46:46 [main] INFO 
com.d_cloud.common.datasource.DataSourceConfig- 添加H79_dev数据源到Map  
2019/02/25-23:48:20 [main] INFO 
com.d_cloud.common.datasource.DataSourceConfig-
添加Map到数据源dynamicDataSource   2019/02/25-23:48:20 [main] INFO 
com.d_cloud.Application- Started Application in 109.951 seconds (JVM
running for 111.267)

多个数据源汇总查询

/**      *项目列表——all      */
    @RequestMapping(value="ProjectListSum",method=RequestMethod.GET)
    @ResponseBody   public List
getProjectListSum(HttpServletRequest request, HttpServletResponse
response, ProjectList info ) throws Exception {

    DataSourceContextHolder.setDataSourceType("H191_dev");
    List

功能扩展

SpringCloud 微服务动态切换数据源

SpringCloud 的各个提供服务者目前是从远程 Git-Config 仓库获取各个服务的配置文件来配置数据库。修改某个服务的数据源需修改远程 Git-Config 仓库;若用此方法可以实现服务的动态切换,数据源加密到加密 token 解析再连接数据源;从而实现单个服务多数据源接口提供。

PaaS 平台应用

PaaS 平台可以针对每个开发者提供数据源配置表。开发者只需将项目周期的各个阶段数据源配置到表里,比如配置(开发,测试,正式数据源)配置到表里,无需开发连接数据相关的工具类。从而减少开发时间,提高开发效率,提高项目复用性。

你可能感兴趣的:(架构)