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

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

spring boot jpa多数据源 springboot多数据源原理_spring boot jpa多数据源

二 具体实现

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