场景:

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. [原创]Delphi 文件函数:ForceDirectories() 函数和 CreateDir函数

    引用单元:SysUtils function ForceDirectories(Dir: string): Boolean;    //创建多级目录  父目录不必存在   (Force 有暴力.强制的 ...

  2. xcode5 添加Build Phases脚本

    http://www.runscriptbuildphase.com/ 版权声明:本文为博主原创文章,未经博主允许不得转载.

  3. spark SQL之Catalog API使用

    Catalog API简介 Spark中的DataSet和Dataframe API支持结构化分析.结构化分析的一个重要的方面是管理元数据.这些元数据可能是一些临时元数据(比如临时表).SQLCont ...

  4. ajax 接收json数据的进一步了解

    var url = "../searchclasses"; $.ajax({ url: url, type: "post", dataType: "j ...

  5. Hadoop安装成功之后,访问不了web界面的50070端口怎么解决?

    Hadoop安装成功之后,访问不了web界面的50070端口 先查看端口是否启用 [hadoop@s128 sbin]$ netstat -ano |grep 50070 然后查看防火墙的状态,是否关 ...

  6. 13-Ubuntu-查阅终端命令版本信息和帮助信息

    查看版本信息: 终端命令 --version 查看帮助信息: 终端命令 --help 注: 待查阅的命令 后面有两个减号-- 例:查看终端命令ls的版本和帮助信息 ls --version ls -- ...

  7. HDU 5531

    题目大意: 给定一个n边形的顶点 以每个顶点为圆心画圆(半径可为0) 每个顶点的圆要和它相邻顶点的圆相切(不相邻的可相交) 求所有圆的最小面积总和并给出所有圆的半径 设半径为r1 r2 ... rn, ...

  8. verifier 调试内存泄露

    没啥技术含量,都是老段子了, 这次记下来,只是我想说,我也做过,留个念相. 前置条件,电脑里面必须得有Verifier,有了之后把自己的驱动加进去, WinDBG上双机,然后就可以跑了,跑一段时间就可 ...

  9. 2019-8-24-win10-本地适配器不支持重要的低能耗控制器状态

    title author date CreateTime categories win10 本地适配器不支持重要的低能耗控制器状态 lindexi 2019-8-24 16:2:33 +0800 20 ...

  10. python open函数关于w+ r+ 读写操作的理解(转)

    r 只能读 (带r的文件必须先存在)r+ 可读可写 不会创建不存在的文件.如果直接写文件,则从顶部开始写,覆盖之前此位置的内容,如果先读后写,则会在文件最后追加内容.w+ 可读可写 如果文件存在 则覆 ...