MyBatis整合双数据源
有时候在项目中会遇到需要连接两个数据库的情况。本文就结合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整合双数据源的更多相关文章
- Spring Boot 集成 Mybatis 实现双数据源
这里用到了Spring Boot + Mybatis + DynamicDataSource配置动态双数据源,可以动态切换数据源实现数据库的读写分离. 添加依赖 加入Mybatis启动器,这里添加了D ...
- spring+mybatis 配置双数据源
配置好后,发现网上已经做好的了, 不过,跟我的稍有不同, 我这里再拿出来现个丑: properties 文件自不必说,关键是这里的xml: <?xml version="1.0&quo ...
- mybatis的双数据源创建
一.jdbc中: jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://202.108.211.55:3306/app-apm?useUnic ...
- Spring Boot 中使用 MyBatis 整合 Druid 多数据源
2017 年 10 月 20 日 Spring Boot 中使用 MyBatis 整合 Druid 多数据源 本文将讲述 spring boot + mybatis + druid 多数据源配置方 ...
- SpringBoot进阶教程 | 第四篇:整合Mybatis实现多数据源
这篇文章主要介绍,通过Spring Boot整合Mybatis后如何实现在一个工程中实现多数据源.同时可实现读写分离. 准备工作 环境: windows jdk 8 maven 3.0 IDEA 创建 ...
- Spring Boot集成Mybatis双数据源
这里用到了Spring Boot + Mybatis + DynamicDataSource配置动态双数据源,可以动态切换数据源实现数据库的读写分离. 添加依赖 加入Mybatis启动器,这里添加了D ...
- 【springboot spring mybatis】看我怎么将springboot与spring整合mybatis与druid数据源
目录 概述 1.mybatis 2.druid 壹:spring整合 2.jdbc.properties 3.mybatis-config.xml 二:java代码 1.mapper 2.servic ...
- 3.springMVC+spring+Mybatis整合Demo(单表的增删该查,这里主要是贴代码,不多解释了)
前面给大家讲了整合的思路和整合的过程,在这里就不在提了,直接把springMVC+spring+Mybatis整合的实例代码(单表的增删改查)贴给大家: 首先是目录结构: 仔细看看这个目录结构:我不详 ...
- Spring+springmvc+Mybatis整合案例 annotation版(myeclipse)详细版
Spring+springmvc+Mybatis整合案例 Version:annotation版 文档结构图: 从底层开始做起: 01.配置web.xml文件 <?xml version=&qu ...
随机推荐
- C/C++数组和指针详解
/****************************************************************/ /* 学习是合作和分享式的! /* Auth ...
- 去除 HTML 和 PHP 标记
strip_tags()函数,从字符串中去除html和php标记,随笔记一下,如果看过就过去,下次再找可能又会费点事,记下来下次好找
- Spring Junit--第一个测试
配置成功后,需要启动测试用例! package com.cml.controller; import javax.annotation.Resource; import org.junit.Test; ...
- spark机器学习从0到1基本的统计工具之(三)
给定一个数据集,数据分析师一般会先观察一下数据集的基本情况,称之为汇总统计或者概要性统计.一般的概要性统计用于概括一系列观测值,包括位置或集中趋势(比如算术平均值.中位数.众数和四分位均值),展型 ...
- AJAX一
一.http协议 1.URL http://www.baidu.com/md/index.html 结构:协议+主机名称+目录结构+文件名称 URL完整的结构 <scheme>://< ...
- Hbase javaAPI报错:Callexception,tries=10,retries=35,started=38465msago
[client.RpcRetryingCaller(142)]Callexception,tries=10,retries=35,started=38465msago,cancelled=false, ...
- IDEA提高开发效率的7个插件
IDEA提高开发效率的7个插件 1. 多行编辑 先来体验一下从xml文件拷贝字段新建实体对象 一般我们为了新建多表连接后映射的 ResultMap ,耗费不少时间,那么我们就来试一试这个多行编辑 表字 ...
- SVM——支持向量机(完整)
最基本的SVM(Support Vector Machine)旨在使用一个超平面,分离线性可分的二类样本,其中正反两类分别在超平面的一侧.SVM算法则是要找出一个最优的超平面. 线性可分SVM 优化函 ...
- excel导入mysql数据
excel加载mysql数据 1.第一步,选择从mysql导入数据 2.单击会出现弹框: 3.可能有的同学的,这里缺少插件,例如: 4.去下载 这个 插件安装即可.https://dev.mysql. ...
- 【HTTP】HTTP报文&状态码
HTTP报文中的HTTP信息 一.编码提升传输速率 编码的好处:有效处理大量的访问请求 编码的弊端:会消耗更多的CPU资源 报文主体&实体主体 报文:HTTP通信的基本单元,8位组字节流组成, ...