title: Spring多数据源动态切换

date: 2019-11-27

categories:

  • Java
  • Spring

    tags:
  • 数据源

    typora-root-url: ......

原理

DataSource向外提供一个 etConnection() 方法,得getConnection者得数据库

“一代雄主” AbstractRoutingDataSource 实现了 getConnection() 方法

	// line 166
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
} ... 省略若干代码 // line 190
/**
* Retrieve the current target DataSource. Determines the
* {@link #determineCurrentLookupKey() current lookup key}, performs
* a lookup in the {@link #setTargetDataSources targetDataSources} map,
* falls back to the specified
* {@link #setDefaultTargetDataSource default target DataSource} if necessary.
* @see #determineCurrentLookupKey()
*/
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
} /**
* Determine the current lookup key. This will typically be
* implemented to check a thread-bound transaction context.
* <p>Allows for arbitrary keys. The returned key needs
* to match the stored lookup key type, as resolved by the
* {@link #resolveSpecifiedLookupKey} method.
*/
@Nullable
protected abstract Object determineCurrentLookupKey();

然而 ....

AbstractRoutingDataSource 的getConnection() 方法只是调用了 determinTargetDataSource().getConnection() 来获取真正DataSource的getConnection()。

这是典型的装饰模式!!自己没有的功能通过引入其他类来增强。

我们先来看看 AbstractRoutingDataSource 的类结构

被框框套住的都是重要的。

方法determineCurrentLookupKey() 是留给我们开发者的(就像你家的网线口),我们通过实现该方法在不同数据源之间切换。

实践

1. 配置多数据源

在 application.yml 如下配置

spring:
datasource:
# 数据源类型
type: com.alibaba.druid.pool.DruidDataSource
# 默认数据源
default-datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db0?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&allowMultiQueries=true&serverTimezone=GMT%2B8
username: root
password: 123456 # 多数据源
target-datasources:
datasource1:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&allowMultiQueries=true&serverTimezone=GMT%2B8
username: root
password: 123456 datasource2:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&allowMultiQueries=true&serverTimezone=GMT%2B8
username: root
password: 123456 # druid 默认配置
druid:
# 初始连接数
initial-size: 10
# 最大连接池数量
max-active: 100
# 最小连接池数量
min-idle: 10
# 配置获取连接等待超时的时间
max-wait: 60000
# 打开PSCache,并且指定每个连接上PSCache的大小
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
stat-view-servlet:
enabled: true
url-pattern: /monitor/druid/*
filter:
stat:
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: false
wall:
config:
multi-statement-allow: true # MyBatis
mybatis:
# 搜索指定包别名
typeAliasesPackage: com.liuchuanv
# 配置mapper的扫描,找到所有的mapper.xml映射文件
mapperLocations: classpath*:mapper/**/*Mapper.xml
# 加载全局的配置文件
configLocation: classpath:mybatis-config.xml

此处配置的名称(如 defaultDataSource、targetDataSources)的命名并无特殊要求,只要和下面第n步的 DataSourceConfig 中对应起来就可以

使用 Druid 数据源的话,要在 pom.xml 中引入依赖

    <!--阿里数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>

2. 实现动态数据源

DynamicDataSource 动态数据源,在多个数据源之间切换

public class DynamicDataSource extends AbstractRoutingDataSource {

    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
} @Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}
}

DataSourceContextHolder 数据源上下文,使用线程变量来存储代表当前使用的数据源的key值(每个key值都对应一个数据源,用以区分多数据源)

public class DataSourceContextHolder {

    public static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<String>();

    public static void setDataSourceType(String dsType) {
CONTEXT_HOLDER.set(dsType);
} public static String getDataSourceType() {
return CONTEXT_HOLDER.get();
} public static void removeDataSourceType() {
CONTEXT_HOLDER.remove();
} }

DataSourceType 数据源对应的key(其实单纯的用字符串来表示数据源,替换枚举类DataSourceType也是可以的,但是写代码时要注意字符串统一)

public enum  DataSourceType {
/** 默认数据源key */
DEFAULT_DATASOURCE, /** 数据源1key*/
DATASOURCE1, /** 数据源2key*/
DATASOURCE2;
}

3. 将数据源添加到 Spring 容器中

@Configuration
public class DataSourceConfig { @Bean
@ConfigurationProperties(prefix = "spring.datasource.default-datasource")
public DataSource defaultDataSource() {
return DruidDataSourceBuilder.create().build();
} @Bean
@ConfigurationProperties(prefix = "spring.datasource.target-datasources.datasource1")
public DataSource dataSource1() {
return DruidDataSourceBuilder.create().build();
} @Bean
@ConfigurationProperties(prefix = "spring.datasource.target-datasources.datasource2")
public DataSource dataSource2() {
return DruidDataSourceBuilder.create().build();
} @Bean
@Primary
public DataSource dynamicDataSource(DataSource defaultDataSource, DataSource dataSource1, DataSource dataSource2) {
// 注意:该方法的参数名称要和前面前面三个datasource对象在Spring容器中的bean名称一样
// 或者使用 @Qualifier 指定具体的bean
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.DEFAULT_DATASOURCE.name(), defaultDataSource);
targetDataSources.put(DataSourceType.DATASOURCE1.name(), dataSource1);
targetDataSources.put(DataSourceType.DATASOURCE2.name(), dataSource2);
return new DynamicDataSource(defaultDataSource, targetDataSources);
}
}

测试

为了方便,省略了 Service 层

TestController

@RestController
@RequestMapping("/test")
public class TestController { @Autowired
private TestMapper testMapper; @GetMapping
public List<Map<String, Object>> test(String dataSourceIndex) {
// 根据参数值的不同,切换数据源
if ("1".equals(dataSourceIndex)) {
DataSourceContextHolder.setDataSourceType(DataSourceType.DATASOURCE1.name());
} else if ("2".equals(dataSourceIndex)) {
DataSourceContextHolder.setDataSourceType(DataSourceType.DATASOURCE2.name());
}
List<Map<String, Object>> mapList = testMapper.selectList();
// 清除线程内部变量数据源key
DataSourceContextHolder.removeDataSourceType();
return mapList;
}
}

TestMapper

@Repository
public interface TestMapper {
/**
* 查询列表
* @return
*/
List<Map<String, Object>> selectList();
}

TestMapper.xml

<?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.liuchuanv.dynamicdatasource.mapper.TestMapper">
<select id="selectList" resultType="java.util.Map">
SELECT * FROM test
</select>
</mapper>

别忘了要准备数据哦!

下面SQL语句,创建3个数据库,然后在3个数据库中都创建一张test表,并各自插入不同的数据。


-- 创建数据库
create database db0 character set utf8 collate utf8_general_ci;
create database db1 character set utf8 collate utf8_general_ci;
create database db2 character set utf8 collate utf8_general_ci; -- 在数据库db1下执行以下SQL
use db0;
create table test(
id int(11) primary key auto_increment,
name varchar(20)
) ;
insert into test(name) values('张三'); -- 在数据库db1下执行以下SQL
use db1;
create table test(
id int(11) primary key auto_increment,
name varchar(20)
) ;
insert into test(name) values('李四'); -- 在数据库db2下执行以下SQL
use db2;
create table test(
id int(11) primary key auto_increment,
name varchar(20)
) ;
insert into test(name) values('王五');

OK,一切准备就绪,启动应用吧!!!

一启动就出现了各种各样的,似乎无穷无尽的报错!一头黑线。

1. 找不到TestMapper

Field testMapper in com.liuchuanv.dynamicdatasource.controller.TestController required a bean of type 'com.liuchuanv.dynamicdatasource.mapper.TestMapper' that could not be found.

解决方法:在 DynamicdatasourceApplication 头上添加注解 @MapperScan("com.liuchuanv.*.mapper")

2. dynamicDataSource 依赖循环

┌─────┐
| dynamicDataSource defined in class path resource [com/liuchuanv/dynamicdatasource/common/DataSourceConfig.class]
↑ ↓
| defaultDataSource defined in class path resource [com/liuchuanv/dynamicdatasource/common/DataSourceConfig.class]
↑ ↓
| org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker
└─────┘

解决方法:在 DynamicdatasourceApplication 头上修改注解 @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})

终于处理好所有的问题,终于能痛痛快快的访问 http://localhost:8080/test

使用的是默认数据源 defaultDataSource

使用的是数据源 dataSource1

使用的是数据源 dataSource2

建议大家在心里总结一下整个的过程,其实很简单

Spring多数据源动态切换的更多相关文章

  1. Spring主从数据源动态切换

    参考文档: http://uule.iteye.com/blog/2126533 http://lanjingling.github.io/2016/02/15/spring-aop-dynamicd ...

  2. spring 多数据源动态切换

    理解spring动态切换数据源,需要对spring具有一定的了解 工作中经常遇到读写分离,数据源切换的问题,那么以下是本作者实际工作中编写的代码  与大家分享一下! 1.定义注解 DataSource ...

  3. Spring Boot 如何动态切换数据源

    本章是一个完整的 Spring Boot 动态数据源切换示例,例如主数据库使用 lionsea 从数据库 lionsea_slave1.lionsea_slave2.只需要在对应的代码上使用 Data ...

  4. 实战:Spring AOP实现多数据源动态切换

    需求背景 去年底,公司项目有一个需求中有个接口需要用到平台.算法.大数据等三个不同数据库的数据进行计算.组装以及最后的展示,当时这个需求是另一个老同事在做,我只是负责自己的部分. 直到今年回来了,这个 ...

  5. Spring3.3 整合 Hibernate3、MyBatis3.2 配置多数据源/动态切换数据源 方法

    一.开篇 这里整合分别采用了Hibernate和MyBatis两大持久层框架,Hibernate主要完成增删改功能和一些单一的对象查询功能,MyBatis主要负责查询功能.所以在出来数据库方言的时候基 ...

  6. Springboot多数据源配置--数据源动态切换

    在上一篇我们介绍了多数据源,但是我们会发现在实际中我们很少直接获取数据源对象进行操作,我们常用的是jdbcTemplate或者是jpa进行操作数据库.那么这一节我们将要介绍怎么进行多数据源动态切换.添 ...

  7. springboot多数据源动态切换和自定义mybatis分页插件

    1.配置多数据源 增加druid依赖 完整pom文件 数据源配置文件 route.datasource.driver-class-name= com.mysql.jdbc.Driver route.d ...

  8. Spring3.3 整合 Hibernate3、MyBatis3.2 配置多数据源/动态切换数据源方法

    一.开篇 这里整合分别采用了Hibernate和MyBatis两大持久层框架,Hibernate主要完成增删改功能和一些单一的对象查询功能,MyBatis主要负责查询功能.所以在出来数据库方言的时候基 ...

  9. mybatis 多数据源动态切换

    笔者主要从事c#开发,近期因为项目需要,搭建了一套spring-cloud微服务框架,集成了eureka服务注册中心. gateway网关过滤.admin服务监控.auth授权体系验证,集成了redi ...

随机推荐

  1. JavaScript 数组去重和对象相等判断

    前几天电话面试问到了数组去重和两个对象相等判断,当时回答的不是特别好,都过去好几天了,总结下. 1.数组去重 当时的问题是这样的有个简单的数组[1,1,2,3],去重后的结果是[1,2,3],怎么实现 ...

  2. 阿里云ECS服务器活动99元一年,最高可买三年

    这几天阿里云 99一年.279三年的服务器活动如火如荼,和之前腾讯三年的服务器非常类似,非常低的价格换取非常高的价值,当然,通常情况下便宜没好货的,想要玩一下的老铁可以进阿里云去看看,阿里云270三年 ...

  3. PHP+MySQL实现对一段时间内每天数据统计优化操作实例

    http://www.jb51.net/article/136685.htm 这篇文章主要介绍了PHP+MySQL实现对一段时间内每天数据统计优化操作,结合具体实例形式分析了php针对mysql查询统 ...

  4. mysql 第一课 win7艰难的配置与删除注册表重新安装

    mysql 这个安装一定要慎重!慎重!慎重! 重要的事情说三遍.. 不会的一定要按步骤来~出错了真的很难受. 详细教程 -->  https://jingyan.baidu.com/album/ ...

  5. 第三章 通过java SDK 实现个性化智能合约的部署与测试

    想了解相关区块链开发,技术提问,请加QQ群:538327407 前提 已经部署好底层,外网可以正常请求访问. 正常流程 1.基础合约处理 https://fisco-bcos-documentatio ...

  6. Ubuntu 19.04安装phpipam软件

    1ftp下载xampp2安装xampp chmod 777sudo ./xampp.run3,ftp phpipam.tar.gz 解压 ./opt/lampp/www/phpipam/cp conf ...

  7. vue通信、传值的方式

    原文博主地址:https://blog.csdn.net/qq_35430000/article/details/79291287 看完还是受益匪浅,讲得很详细..感谢!

  8. The bind() Method

    The bind() method was added in ESMAScript 5, but it is easy to simulate in ESMAScrpt 3. As its name ...

  9. H3C 端口绑定基本配置

  10. Linux 内核总线方法

    有几个给 bus_type 结构定义的方法; 它们允许总线代码作为一个设备核心和单独驱动之 间的中介. 在 2.6.10 内核中定义的方法是: int (*match)(struct device * ...