一 springboot 实现多数据源的底层原理
任何的ORM Framework无论上层如何如何,最终都是通过各种层的调用,来到spring-jdbc来调用操作DB的,而在springboot框架中,其本质上是通过我们注入的datasource(无论是自定义的datasource还是第三方组件提供的现成的datasource)的getconnection,来获取一个spring-jdbc的连接,最终提供给持久层框架,来操作db。基于这一点的理解,所以要实现多数据就有理论的操作依据了。从以下的执行流程图也可以很直观的看出来。
二 具体实现
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;/**
- 动态数据源的原理
- ORM Framework 通过自定义的datasource,根据spring 注入的datasource
- 拿到 注入的datasource,通过 datasource提供的getConnection()来获取一个connection对象,这就是
- connection对象就是最终ORM通过spring-jdbc来对DB进行操作的connection
- 通过一个全局标识,来进行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和一个自定义的标识符,进行动态切换,就可以模拟出多数据源的动态切换原理,当然,实际应用中肯定不提倡这个做法,在这个实验中,只是为了说明上面的多数据源实现原理,后面我们会通过第二种方式,步步进行学习,达到知其然还要知其所以然的效果。