场景:

springboot项目,默认使用HikariCP连接池 + MybatisPlus持久层框架 + mysql数据库等一套流程,现需求需去第三方sqlserver数据库拉取数据,直连数据库,不走接口,因此,需把项目改造成 多数据源结构,以实现动态切换数据源。

使用docker 安装mysql + sqlserver 数据库 进行测试

实现示例:

0.pom.xml

 <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.mumu</groupId>
<artifactId>springboot-mumu</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>mumu-web</artifactId> <dependencies>
<!-- web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- sqlserver -->
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<scope>runtime</scope>
</dependency>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency> </dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> </project>

1.配置文件

server:
port: 9587
spring:
datasource:
hikari:
master:
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://127.0.0.1:3306/d_credit?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8
username: root
password: 1234567890
slave:
driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
jdbcUrl: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=TestDB
username: SA
password: <1234567890@Passw0rd>
#mybatis-plus
mybatis-plus:
type-aliases-package: com.mumu.model
mapper-locations: classpath:/mapper/**/*.xml
configuration:
jdbc-type-for-null: null
map-underscore-to-camel-case: true
cache-enabled: false
global-config:
db-config:
id-type: auto
field-strategy: not_empty

2.数据源配置类

注意:!!!!!

1.使用mybatis的全局配置文件  这里工厂bean使用SqlSessionFactoryBean

使用mybatis-plus的全局配置 这里工厂bean使用MybatisSqlSessionFactoryBean

否则 mybatis对应的全局配置不会生效

2.@Primary用在DataSourceBuilder.create().build()构建的DataSource方法上,不能放到构建动态数据源方法上,否则会有循环依赖的问题


package com.mumu.common.config;

import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.mumu.common.datasources.DataSourceNames;
import com.mumu.common.datasources.DynamicDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.sql.DataSource;
import javax.xml.crypto.Data;
import java.util.HashMap;
import java.util.Map; /**
* @Description
* @Author Created by Mumu
* @Date on 2019/11/25
*/
@Configuration
@EnableTransactionManagement
@MapperScan("com.mumu.*.mapper")
public class DataSourceConfig { @Primary
@Bean(name = "master")
@ConfigurationProperties(prefix = "spring.datasource.hikari.master")
public DataSource master() {
return DataSourceBuilder.create().build();
} @Bean(name = "slave")
@ConfigurationProperties(prefix = "spring.datasource.hikari.slave")
public DataSource slave() {
return DataSourceBuilder.create().build();
} @Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>(2);
dataSourceMap.put(DataSourceNames.FIRST, master());
dataSourceMap.put(DataSourceNames.SECOND, slave());
/// 将 master 数据源作为默认指定的数据源
dynamicDataSource.setDefaultDataSource(master());
// 将 master 和 slave 数据源作为指定的数据源
dynamicDataSource.setDataSources(dataSourceMap);
return dynamicDataSource;
} @Bean("sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory() throws Exception {
// SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); // 使用mybatis的全局配置文件
//使用mybatisplus的工程bean,mybatis-plus的全局配置文件才会生效
MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
// 配置数据源,此处配置为关键配置,如果没有将 dynamicDataSource作为数据源则不能实现切换
sqlSessionFactoryBean.setDataSource(dynamicDataSource());
// 扫描model
sqlSessionFactoryBean.setTypeAliasesPackage("com.mumu.model");
// 扫描映射文件
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:/mapper/**/*Mapper.xml"));
return sqlSessionFactoryBean.getObject();
}
}
 

 3.动态数据源类

我们上一步把这个动态数据源设置到了SQL会话工厂和事务管理器,这样在操作数据库时就会通过动态数据源类来获取要操作的数据源了。

动态数据源类集成了Spring提供的AbstractRoutingDataSource类,AbstractRoutingDataSource 中获取数据源的方法就是 determineTargetDataSource,而此方法又通过 determineCurrentLookupKey 方法获取查询数据源的key。

所以如果我们需要动态切换数据源,就可以通过以下两种方式定制:

1. 覆写 determineCurrentLookupKey 方法

通过覆写 determineCurrentLookupKey 方法,从一个自定义的 DynamicDataSourceContextHolder.getDataSourceKey() 获取数据源key值,这样在我们想动态切换数据源的时候,只要通过  DynamicDataSourceContextHolder.setDataSourceKey(key)  的方式就可以动态改变数据源了。这种方式要求在获取数据源之前,要先初始化各个数据源到 DynamicDataSource 中,我们案例就是采用这种方式实现的,所以在 MybatisConfig 中把master和slave数据源都事先初始化到DynamicDataSource 中。

2. 可以通过覆写 determineTargetDataSource,因为数据源就是在这个方法创建并返回的,所以这种方式就比较自由了,支持到任何你希望的地方读取数据源信息,只要最终返回一个 DataSource 的实现类即可。比如你可以到数据库、本地文件、网络接口等方式读取到数据源信息然后返回相应的数据源对象就可以了。

 package com.mumu.common.datasources;

 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

 import java.util.Map;

 /**
* @Description 动态数据源实现类
* @Author Created by Mumu
* @Date on 2019/11/25
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 如果希望所有数据源在启动配置时就加载好,这里通过设置数据源Key值来切换数据,定制这个方法
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceKey();
} /**
* 设置默认数据源
* @param defaultDataSource
*/
public void setDefaultDataSource(Object defaultDataSource) {
super.setDefaultTargetDataSource(defaultDataSource);
} /**
* 设置数据源
* @param dataSources
*/
public void setDataSources(Map<Object, Object> dataSources) {
super.setTargetDataSources(dataSources);
// 将数据源的 key 放到数据源上下文的 key 集合中,用于切换时判断数据源是否有效
// DynamicDataSourceContextHolder.addDataSourceKeys(dataSources.keySet());
}
}

4.动态数据源上下文

动态数据源的切换主要是通过调用这个类的方法来完成的

 package com.mumu.common.datasources;

 /**
* @Description 动态数据源上下文
* @Author Created by Mumu
* @Date on 2019/11/25
*/
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {
/**
* 将 master 数据源的 key作为默认数据源的 key
*/
@Override
protected String initialValue() {
return DataSourceNames.FIRST;
}
}; /**
* 获取数据源
* @return
*/
public static String getDataSourceKey() {
return contextHolder.get();
} /**
* 切换数据源
* @param key
*/
public static void setDataSourceKey(String key) {
contextHolder.set(key);
} /**
* 重置数据源
*/
public static void clearDataSourceKey() {
contextHolder.remove();
} }

5.动态数据源注解

 package com.mumu.common.datasources.annotation;

 import java.lang.annotation.*;

 /**
* @Description 动态数据源注解
* @Author Created by Mumu
* @Date on 2019/11/25
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
/**
* 数据源key值
*
* @return
*/
String name() default "";
}

6.动态数据源切换处理器

创建一个AOP切面,拦截带 @DataSource 注解的方法,在方法执行前切换至目标数据源,执行完成后恢复到默认数据源。

 package com.mumu.common.datasources.aspect;

 import com.mumu.common.datasources.DataSourceNames;
import com.mumu.common.datasources.DynamicDataSourceContextHolder;
import com.mumu.common.datasources.annotation.DataSource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component; import java.lang.reflect.Method; /**
* @Description 动态数据源切换处理器
* @Author Created by Mumu
* @Date on 2019/11/25
*/
@Aspect
@Component
@Slf4j
@Order(-1)// 该切面应当先于 @Transactional 执行
public class DynamicDataSourceAspect {
@Pointcut("@annotation(com.mumu.common.datasources.annotation.DataSource)")
public void dataSourcePointCut() { } @Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod(); DataSource ds = method.getAnnotation(DataSource.class);
if (ds == null) {
DynamicDataSourceContextHolder.setDataSourceKey(DataSourceNames.FIRST);
log.info("set datasource is " + DataSourceNames.FIRST);
} else {
// 切换数据源
DynamicDataSourceContextHolder.setDataSourceKey(ds.name());
log.info("Switch DataSource to【{}】in Method【{}】", DynamicDataSourceContextHolder.getDataSourceKey(), signature);
} try {
return point.proceed();
} finally {
// 将数据源置为默认数据源
DynamicDataSourceContextHolder.clearDataSourceKey();
log.info("Restore DataSource to【{}】in Method【{}】", DynamicDataSourceContextHolder.getDataSourceKey(), signature);
}
}
}

7.测试

默认操作mysql数据源(master),不做特殊处理

 package com.mumu.service.impl;

 import com.mumu.common.datasources.DataSourceNames;
import com.mumu.common.datasources.annotation.DataSource;
import com.mumu.model.AddressBook;
import com.mumu.persistence.mapper.AddressBookMapper;
import com.mumu.service.AddressBookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; /**
* @Description
* @Author Created by Mumu
* @Date on 2019/11/25
*/
@Service
public class AddressBookServiceImpl implements AddressBookService {
@Autowired
private AddressBookMapper addressBookMapper; @Override
public AddressBook queryById(Integer bookId){
AddressBook addressBook = addressBookMapper.queryById(bookId);
return addressBook;
}
}

动态切换sqlserver数据源(slave) 通过@DataSource(name = DataSourceNames.SECOND)完成

 package com.mumu.service.impl;

 import com.mumu.common.datasources.DataSourceNames;
import com.mumu.common.datasources.annotation.DataSource;
import com.mumu.model.Inventory;
import com.mumu.persistence.mapper.InventoryMapper;
import com.mumu.service.InventoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; /**
* @Description
* @Author Created by Mumu
* @Date on 2019/11/25
*/
@Service
public class InventoryServiceImpl implements InventoryService {
@Autowired
private InventoryMapper inventoryMapper;
@Override
@DataSource(name = DataSourceNames.SECOND)
public Inventory queryById(Integer id){
return inventoryMapper.queryById(id);
};

 8.遇到的问题

多数据源注入、循环依赖问题

自定义sqlSessionFactory时,用错mybatis/mybatis-plus的类,导致对应的mybatis全局配置文件未生效

敬请期待...

springboot-配置多数据源(AOP实现)(HikariCP + MybatisPlus + mysql + SqlServer)的更多相关文章

  1. springboot配置Druid数据源

    springboot配置druid数据源 Author:SimpleWu springboot整合篇 前言 对于数据访问层,无论是Sql还是NoSql,SpringBoot默认采用整合SpringDa ...

  2. SpringBoot配置多数据源时遇到的问题

    SpringBoot配置多数据源 参考代码:Spring Boot 1.5.8.RELEASE同时配置Oracle和MySQL 原作者用的是1.5.8版本的SpringBoot,在升级到2.0.*之后 ...

  3. springboot 配置多数据源

    1.首先在创建应用对象时引入autoConfig package com; import org.springframework.boot.SpringApplication; import org. ...

  4. springboot 配置多数据源 good

    1.首先在创建应用对象时引入autoConfig package com; import org.springframework.boot.SpringApplication; import org. ...

  5. SpringBoot配置多数据源Mysql+Sqlite

    ​ 配置了一下druid的多数据源配置,尝试了很多方法,Spring boot关于对Mysql和Sqlite多数据源的配置,记录下来: 涉及技术点: Springboot + Druid + Mysq ...

  6. springboot配置多数据源mybatis配置失效问题

    mybatis配置 #开启驼峰映射 mybatis.configuration.map-underscore-to-camel-case=true #开启打印sql mybatis.configura ...

  7. SpringBoot配置多数据源

    原文:https://www.jianshu.com/p/033e0ebeb617 项目中用到了两个数据库,分别是Oracle和Mysql,涉及到了多数据源问题,这里做下记录 官方讲解:https:/ ...

  8. springboot配置多数据源(JdbcTemplate方式)

    在实际开发中可能会遇到需要配置多个数据源的情况,比如:需要使用多个host.需要使用多种数据库(MySql.Oracle.SqlServer…) 如果使用springboot开发,可做如下配置: Co ...

  9. Springboot配置多数据源(Mysql和Orcale)--(Idea Maven JDBCTemplate支持下的)

    1.配置 orcale jdbc 对于一个Maven项目,使用Mysql时,可直接添加如下依赖: <dependency> <groupId>mysql</groupId ...

随机推荐

  1. Servlet(Server Applet) 详解

    Java编写的服务器端程序.其主要功能在于交互式地浏览和修改数据,生成动态Web内容. Servlet的工作模式 客户端发送请求至服务器 服务器启动并调用Servlet,Servlet根据客户端请求生 ...

  2. Go const 关键字

    Go const 关键字 package main import "fmt" func main() { const LENGTH int = 10 const WIDTH int ...

  3. css属性大全(基础篇)

      什么是CSS? CSS全称为Cascading Style Sheets,中文翻译为“层叠样式表”,简称CSS样式表,所以称之为层叠样式表(Cascading Stylesheet)简称CSS.在 ...

  4. APIO 2007 风铃

    题目描述 你准备给弟弟 Ike 买一件礼物,但是,Ike 挑选礼物的方式很特别:他只喜欢那些能被他排成有序形状的东西. 你准备给 Ike 买一个风铃.风铃是一种多层的装饰品,一般挂在天花板上. 每个风 ...

  5. [luogu 4389] 付公主的背包

    题意:求一个较大的多重背包对于每个i的方案数,答案对998244353取模. 思路: 生成函数: 对于一个\(V\) 设: \(f(x) = \sum_{i=0}^{oo} x ^ {V * i} = ...

  6. ionic-CSS:ionic checkbox(复选框)

    ylbtech-ionic-CSS:ionic checkbox(复选框) 1.返回顶部 1. ionic checkbox(复选框) ionic 里面的 Checkbox 和普通的 Checkbox ...

  7. JVM内核-原理、诊断与优化学习笔记(二):JVM运行机制

    文章目录 JVM启动流程 PC寄存器 方法区 保存装载的类信息 通常和永久区(Perm)关联在一起 Java堆 Java栈 Java栈 – 局部变量表 ** 包含参数和局部变量 ** Java栈 – ...

  8. TVS(瞬态抑制二极管)、Schottky(肖特基二极管)、Zener (齐纳二极管,也称稳压二极管)主要特点及区别和使用

    1. 简单介绍 TVS TVS(Transient Voltage Suppressor)二极管,又称为瞬态抑制二极管,是普遍使用的一种新型高效电路保护器件,它具有极快的响应时间(亚纳秒级)和相当高的 ...

  9. 剑指offer——36二叉树和为某一值的路径

    题目描述 输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径.路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径.(注意: 在返回值的list中,数组长度大 ...

  10. Python3数据科学入门与实践✍✍✍

    Python3数据科学入门与实践  整个课程都看完了,这个课程的分享可以往下看,下面有链接,之前做java开发也做了一些年头,也分享下自己看这个视频的感受,单论单个知识点课程本身没问题,大家看的时候可 ...