springboot 集成多数据源 一 原理分析

一 springboot 实现多数据源的底层原理

任何的ORM Framework无论上层如何如何,最终都是通过各种层的调用,来到spring-jdbc来调用操作DB的,而在springboot框架中,其本质上是通过我们注入的datasource(无论是自定义的datasource还是第三方组件提供的现成的datasource)的getconnection,来获取一个spring-jdbc的连接,最终提供给持久层框架,来操作db。基于这一点的理解,所以要实现多数据就有理论的操作依据了。从以下的执行流程图也可以很直观的看出来。

image

二 具体实现

1.添加自定义的datasource,注入我们需要注入的多数据源

多数据源的Bean

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.study.dynamicdatasource.DynamicdatasourceApplication;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(DataSourceConfig.class);

@Bean(name="ds1")
@ConfigurationProperties("spring.datasource.datasource1")
public DataSource dataSource1()
{
    logger.info("dataSource1 is created...");
    return DruidDataSourceBuilder.create().build();
}

@Bean(name="ds2")
@ConfigurationProperties("spring.datasource.datasource2")
public DataSource dataSource2()
{
    logger.info("dataSource2 is created...");
    return DruidDataSourceBuilder.create().build();
}

}

自定义数据源的Bean

import com.alibaba.druid.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

/**

  • 动态数据源的原理
    1. ORM Framework 通过自定义的datasource,根据spring 注入的datasource
    1. 拿到 注入的datasource,通过 datasource提供的getConnection()来获取一个connection对象,这就是
  • connection对象就是最终ORM通过spring-jdbc来对DB进行操作的connection
    1. 通过一个全局标识,来进行connection对象的切换,从而实现所谓的动态数据源。
  • 自定义的动态数据源datasource
  • 1.为测试方便,主要是实现getConnection()这个来实现数据源的切换
  • 2.实现InitializingBean接口,是用于容器初始化完成时,初始化ThreadLocal参数的默认值
  • 防止spring容器启动的时候,不知道要首先使用哪个connection对象
  • @author freew
    */

/**

  • 这里添加 @Primary 是因为我们的代码中还有一个DataSourceConfig配置的两个ds,这时候代码中实际上是有
  • 三个datasource,默认使用datasource这个Bean,而我们的容器中当前没有任何一个名字叫 datasource的bean,所以这里加上@primary
    */

/**

  • 使用直接实现 DataSource 来实现多数据源

  • 缺点:很多方法没有实现,难免在使用中会报错,只做为理解来使用,完全不建议在实际中使用
    */
    @Component
    //@Primary
    @Slf4j
    public class MyDynamicDataSource implements DataSource, InitializingBean {
    private static final org.slf4j.Logger logger = LoggerFactory.getLogger(MyDynamicDataSource.class);

    /**

    • 注入我们准备好的两个ds,准备进行数据源切换使用
    • @return
    • @throws SQLException
      */
      @Autowired
      private DataSource ds1;
      @Autowired
      private DataSource ds2;

    /**

    • 实现上面的原理中的第二步,根据标识符进行,动态返回connection对象,完成数据源切换

    • @return Connection

    • @throws SQLException
      */
      @Override
      public Connection getConnection() throws SQLException {
      if (StringUtils.equalsIgnoreCase(CommonConstant.ds_LookupKey.get(), "w")) {
      System.out.println("使用数据源1,进行写操作");
      return ds1.getConnection();
      }

      if (StringUtils.equalsIgnoreCase(CommonConstant.ds_LookupKey.get(), "r")) {
      System.out.println("使用数据源2,进行读操作");
      return ds2.getConnection();
      }
      return ds1.getConnection();
      }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
    return null;
    }

    @Override
    public T unwrap(Class iface) throws SQLException {
    return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
    return false;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
    return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
    return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
    return null;
    }

    /**

    • 用于spring 容器加载后,进行回调的接口,可以用于一些值的初始化,即一参数的默认值设置或者别的操作
    • @throws Exception
      */
      @Override
      public void afterPropertiesSet() throws Exception {
      logger.info("spring 容器加载完毕,开始初始化参数值默认值....");
      CommonConstant.ds_LookupKey.set("w");
      }
      }

2.添加数据源切换的标识符,方便实时切换。

简单的帮助类 public class CommonConstant
{ /**用于全局切换数据源的标识**/ public static ThreadLocal ds_LookupKey=new ThreadLocal<>(); }

三 代码测试

controller代码 集成mybatis非本文重点,请自行百度 import com.study.cloud.springbootdubbo.common.JsonVo;
import com.study.cloud.springbootdubbo.entity.User;
import com.study.cloud.springbootdubbo.service.UserService; import com.study.dynamicdatasource.config.CommonConstant; import com.study.dynamicdatasource.config.MyDynamicDataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;

import java.util.Date;
import java.util.List;
import java.util.Random;

@RestController
@RequestMapping("user")
public class UserController {
@Autowired
UserService userServer;

@PostMapping("/new")
public JsonVo<User> addUser()
{
    CommonConstant.ds_LookupKey.set("w");
    User user=new User().builder()
            .name(String.valueOf(new Random().nextInt(1000)))
            .money(new Random().nextInt())
            .createTime(new Date())
            .build();
    userServer.addUser(user);
    return JsonVo.<User>builder().code(0).data(user).build();
}

@PostMapping("/list")
public JsonVo<User> list()
{
    CommonConstant.ds_LookupKey.set("r");
    List<User> userList= userServer.getList();
    return JsonVo.<User>builder().code(0).dataList(userList).build();
}

}

四 总结

通过设置一个自定义的datasource和一个自定义的标识符,进行动态切换,就可以模拟出多数据源的动态切换原理,当然,实际应用中肯定不提倡这个做法,在这个实验中,只是为了说明上面的多数据源实现原理,后面我们会通过第二种方式,步步进行学习,达到知其然还要知其所以然的效果。


说明:
本代码思想逻辑以及配图,均来自对大师视频的学习,通过自己的总结而来,本人并无视频中的提到的课件,以下代码均为自己所写。配图如有侵权,请联系删除。
原学习视频地址: https://www.bilibili.com/video/BV1YT4y1X7v7?p=3&spm_id_from=pageDriver
最后感谢大师无私奉献学习视频,至敬大师的作品。请有学习时间的同学,可以观看原大师的作品。

posted @ 2022-03-23 15:22  不卷轮子锅  阅读(514)  评论(0编辑  收藏  举报