有时候在项目中会遇到需要连接两个数据库的情况。本文就结合Spring和Mybatis来讲下怎么使用双数据源(或者是多数据源)。

背景知识介绍

本文中实现多数据源的关键是Spring提供的AbstractRoutingDataSource。这个类可以根据lookup key来实现底层数据源的动态转换。

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {

	@Nullable
private Map<Object, Object> targetDataSources; @Nullable
private Object defaultTargetDataSource; private boolean lenientFallback = true; private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup(); @Nullable
private Map<Object, DataSource> resolvedDataSources; @Nullable
private DataSource resolvedDefaultDataSource; public void setTargetDataSources(Map<Object, Object> targetDataSources) {
this.targetDataSources = targetDataSources;
} public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
this.defaultTargetDataSource = defaultTargetDataSource;
} public void setLenientFallback(boolean lenientFallback) {
this.lenientFallback = lenientFallback;
} public void setDataSourceLookup(@Nullable DataSourceLookup dataSourceLookup) {
this.dataSourceLookup = (dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup());
} @Override
public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
}
this.resolvedDataSources = new HashMap<>(this.targetDataSources.size());
this.targetDataSources.forEach((key, value) -> {
Object lookupKey = resolveSpecifiedLookupKey(key);
DataSource dataSource = resolveSpecifiedDataSource(value);
this.resolvedDataSources.put(lookupKey, dataSource);
});
if (this.defaultTargetDataSource != null) {
this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
}
} protected Object resolveSpecifiedLookupKey(Object lookupKey) {
return lookupKey;
} protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
if (dataSource instanceof DataSource) {
return (DataSource) dataSource;
}
else if (dataSource instanceof String) {
return this.dataSourceLookup.getDataSource((String) dataSource);
}
else {
throw new IllegalArgumentException(
"Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
}
} @Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
} @Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
} @Override
@SuppressWarnings("unchecked")
public <T> T unwrap(Class<T> iface) throws SQLException {
if (iface.isInstance(this)) {
return (T) this;
}
return determineTargetDataSource().unwrap(iface);
} @Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return (iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface));
} 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;
} @Nullable
//一般只需要用户实现这个方法。
protected abstract Object determineCurrentLookupKey(); }

实现流程

step1:实现一个自定义的AbstractRoutingDataSource

public class DynamicDataSource extends AbstractRoutingDataSource {

    //这边定义了一个和线程绑定的ThreadLocal变量,用于存放需要使用的数据源的名称
private static final ThreadLocal<String> dataSourceNameHolder = new ThreadLocal<>(); public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
} @Override
//重写了AbstractRoutingDataSource的determineCurrentLookupKey方法
protected Object determineCurrentLookupKey() {
return getDataSource();
} public static void setDataSource(String dataSource) {
dataSourceNameHolder.set(dataSource);
} public static String getDataSource() {
return dataSourceNameHolder.get();
} public static void clearDataSource() {
dataSourceNameHolder.remove();
} }

step2:实现一个AOP对Service层方法进行AOP拦截,调用DynamicDataSource中的ThreadLocal变量,将当前请求需要使用的数据源名称设置进去。

//定义一个DataSource注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String name() default "";
}
//这边再定义一个常量
public interface DataSourceNames {
String FIRST = "first";
String SECOND = "second"; }

定义AOP处理DataSource注解

@Aspect
@Component
public class DataSourceAspect implements Ordered {
protected Logger logger = LoggerFactory.getLogger(getClass()); @Pointcut("@annotation(com.xx.yy.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){
DynamicDataSource.setDataSource(DataSourceNames.FIRST);
logger.debug("set datasource is " + DataSourceNames.FIRST);
}else {
DynamicDataSource.setDataSource(ds.name());
logger.debug("set datasource is " + ds.name());
}
try {
return point.proceed();
} finally {
DynamicDataSource.clearDataSource();
logger.debug("clean datasource");
}
}
@Override
public int getOrder() {
return 1;
}
}

step3:对数据源进行配置

@Configuration
public class DynamicDataSourceConfig { @Bean
@ConfigurationProperties("spring.datasource.druid.first")
public DataSource firstDataSource(){
return DruidDataSourceBuilder.create().build();
} @Bean
@ConfigurationProperties("spring.datasource.druid.second")
public DataSource secondDataSource(){
DataSource dataSource = DruidDataSourceBuilder.create().build();
return dataSource;
} @Bean
@Primary
@DependsOn(value = {"firstDataSource","secondDataSource"})
public DynamicDataSource dataSource(DataSource firstDataSource,DataSource secondDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceNames.FIRST, firstDataSource);
targetDataSources.put(DataSourceNames.SECOND, secondDataSource);
return new DynamicDataSource(firstDataSource, targetDataSources);
}
}

以上就是实现双数据源的全部配置。

使用

使用的时候非常简单,只需要在Service层的方法上加上@DataSource注解就可以了。

@DataSource(name = DataSourceNames.SECOND)
public String selectByInfoName(String name){
//...
}

一些注意点

如果你使用了pageHelper等分页插件,请将方言设置成自动模式, autoRuntimeDialect: true

pagehelper:
reasonable: false
supportMethodsArguments: true
params: count=countSql
autoRuntimeDialect: true

如果你使用了Druid数据源,并通过下面的形式创建数据源,要保障数据源的用户名和密码字段不为null。不然DruidDataSourceWrapper这个Bean会检测这个字段的值,导致启动失败。


@Bean
@ConfigurationProperties("spring.datasource.druid.first")
public DataSource firstDataSource(){
return DruidDataSourceBuilder.create().build();
} @Bean
@ConfigurationProperties("spring.datasource.druid.second")
public DataSource secondDataSource(){
DataSource dataSource = DruidDataSourceBuilder.create().build();
return dataSource;
}

MyBatis整合双数据源的更多相关文章

  1. Spring Boot 集成 Mybatis 实现双数据源

    这里用到了Spring Boot + Mybatis + DynamicDataSource配置动态双数据源,可以动态切换数据源实现数据库的读写分离. 添加依赖 加入Mybatis启动器,这里添加了D ...

  2. spring+mybatis 配置双数据源

    配置好后,发现网上已经做好的了, 不过,跟我的稍有不同, 我这里再拿出来现个丑: properties 文件自不必说,关键是这里的xml: <?xml version="1.0&quo ...

  3. mybatis的双数据源创建

    一.jdbc中: jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://202.108.211.55:3306/app-apm?useUnic ...

  4. Spring Boot 中使用 MyBatis 整合 Druid 多数据源

    2017 年 10 月 20 日   Spring Boot 中使用 MyBatis 整合 Druid 多数据源 本文将讲述 spring boot + mybatis + druid 多数据源配置方 ...

  5. SpringBoot进阶教程 | 第四篇:整合Mybatis实现多数据源

    这篇文章主要介绍,通过Spring Boot整合Mybatis后如何实现在一个工程中实现多数据源.同时可实现读写分离. 准备工作 环境: windows jdk 8 maven 3.0 IDEA 创建 ...

  6. Spring Boot集成Mybatis双数据源

    这里用到了Spring Boot + Mybatis + DynamicDataSource配置动态双数据源,可以动态切换数据源实现数据库的读写分离. 添加依赖 加入Mybatis启动器,这里添加了D ...

  7. 【springboot spring mybatis】看我怎么将springboot与spring整合mybatis与druid数据源

    目录 概述 1.mybatis 2.druid 壹:spring整合 2.jdbc.properties 3.mybatis-config.xml 二:java代码 1.mapper 2.servic ...

  8. 3.springMVC+spring+Mybatis整合Demo(单表的增删该查,这里主要是贴代码,不多解释了)

    前面给大家讲了整合的思路和整合的过程,在这里就不在提了,直接把springMVC+spring+Mybatis整合的实例代码(单表的增删改查)贴给大家: 首先是目录结构: 仔细看看这个目录结构:我不详 ...

  9. Spring+springmvc+Mybatis整合案例 annotation版(myeclipse)详细版

    Spring+springmvc+Mybatis整合案例 Version:annotation版 文档结构图: 从底层开始做起: 01.配置web.xml文件 <?xml version=&qu ...

随机推荐

  1. 微信浏览器中禁止下拉出现网页由xxx.xxxxx.com提供,QQ浏览器X5内核提供技术支持这个

    直接上代码 window.onload = function(){ document.body.addEventListener('touchmove', function (e) { e.preve ...

  2. Crash-fix-2:org.springframework.http.converter.HttpMessageNotReadableException

    最近开始对APP上的Crash进行对应,发现有好多常见的问题,同一个问题在多个APP都类似的出现了,这里记录下这些常见的错误. crash Log: org.springframework.http. ...

  3. filebeat-kafka:WARN producer/broker/0 maximum request accumulated, waiting for space

    You need to configure 3 things: Brokers Filebeat kafka output Consumer Here a example (change paths ...

  4. ambari添加新的服务出错

    错误信息 : raise ExecutionFailed(err_msg, code, out, err) resource_management.core.exceptions.ExecutionF ...

  5. centos7安装后不能连接外网

    测试外网: 1,测访问外网能力:curl -l http://www.baidu.com 2,测访问外网能力:wget http://www.baidu.com 注:ping命令不一定能正确反映系统的 ...

  6. 获取访问用户的客户端IP(适用于公网与局域网).

     /**   * 获取访问用户的客户端IP(适用于公网与局域网).   */   public final String getIpAddr(final HttpServletRequest requ ...

  7. BZOJ 1070 拆点 费用流

    1070: [SCOI2007]修车 Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 5860  Solved: 2487[Submit][Status] ...

  8. Spring Boot 教程 (4) - swagger-ui

    Spring Boot 教程 - swagger-ui 1. 什么是Swagger? Swagger™的目标是为REST APIs 定义一个标准的,与语言无关的接口,使人和计算机在看不到源码或者看不到 ...

  9. SSM——[/WEB-INF/applicationContext.xml] is invalid; nested exception is org.xml.sax.SAXParseException; cvc-elt.1: 找不到元素 'beans' 的声明。

    报错文件:/SSM_Integration/WebContent/WEB-INF/applicationContext.xml <beans xmlns="http://www.spr ...

  10. CentOS下搭建Git服务器

    1.首先需要安装Git,可以使用yum源在线安装: [root@localhost Desktop]# yum install -y git 2.创建一个git用户,用来运行git服务 # addus ...