需求:项目使用了读写分离,或者数据进行了分库处理,我们希望在操作不同的数据库的时候,我们的程序能够动态的切换到相应的数据库,执行相关的操作。

首先,你需要一个能够正常运行的springboot项目,配置mybatis并且能够正常的操作数据库(增删查改)

现在开始实现:

思路:现在项目的结构设计基本上是基于MVC的,那么数据库的操作集中在dao层完成,主要业务逻辑在service层处理,controller层处理请求。假设在执行dao层代码之前能够将数据源(DataSource)换成我们想要执行操作的数据源,那么这个问题就解决了。

虽然思路是有了,但是怎么换呢?
爬了很多的博客查看了下官方的文档,我找到了这样一个类:AbstractRoutingDataSource,它继承于AbstractDataSourceAbstractDataSource又实现了DataSource接口,是一个标准的数据源。

查看AbstractRoutingDataSource类:

/*** Abstract {@link javax.sql.DataSource} implementation that routes {@link #getConnection()}* calls to one of various target DataSources based on a lookup key. The latter is usually* (but not necessarily) determined through some thread-bound transaction context.** @author Juergen Hoeller* @since 2.0.1* @see #setTargetDataSources* @see #setDefaultTargetDataSource* @see #determineCurrentLookupKey()*///翻译结果如下
/*** 抽象 {@link javax.sql.DataSource} 路由 {@link #getConnection ()} 的实现* 根据查找键调用不同的目标数据之一。后者通常是* (但不一定) 通过某些线程绑定事务上下文来确定。** @author 史塔克 Hoeller* @since 2.0。1* @see #setTargetDataSources* @see #setDefaultTargetDataSource* @see #determineCurrentLookupKey ()*/
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {......./*** Specify the map of target DataSources, with the lookup key as key.* The mapped value can either be a corresponding {@link javax.sql.DataSource}* instance or a data source name String (to be resolved via a* {@link #setDataSourceLookup DataSourceLookup}).* <p>The key can be of arbitrary type; this class implements the* generic lookup process only. The concrete key representation will* be handled by {@link #resolveSpecifiedLookupKey(Object)} and* {@link #determineCurrentLookupKey()}.*///翻译如下/***指定目标数据源的映射,查找键为键。*映射的值可以是相应的{@link javax.sql.DataSource}*实例或数据源名称字符串(要通过* {@link #setDataSourceLookup DataSourceLookup})。*键可以是任意类型的; 这个类实现了*通用查找过程只。 具体的关键表示将*由{@link #resolveSpecifiedLookupKey(Object)}和* {@link #determineCurrentLookupKey()}。*/public void setTargetDataSources(Map<Object, Object> targetDataSources) {this.targetDataSources = targetDataSources;}....../*** 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.*///翻译如下/*** 确定当前的查找键。这通常会* 实现以检查线程绑定的事务上下文。* <p> 允许任意键。返回的密钥需要* 与存储的查找密钥类型匹配, 如* {@link #resolveSpecifiedLookupKey} 方法。*/protected abstract Object determineCurrentLookupKey();}

我这里只是选择性的贴出了部分源码,详细信息大家可以在IDE里直接点进去查看。翻译的很鸡肋,但是可以明白,这个类的基本运作方式,它是一个abstract类,所以我们使用的话,推荐的方式是创建一个类来继承它并且实现它的determineCurrentLookupKey()方法,这个方法介绍上面也进行了说明,就是通过这个方法进行数据源的切换,这个时候你会又疑问,我没设置数据源,它是怎么切换的?上面我贴出了另外一个核心的方法setTargetDataSources(Map<Object, Object> targetDataSources),它需要一个Map,在方法注释中我们可以得知,这个Map存储的就是我们配置的多个数据源的键值对。我们整理一下这个类切换数据源的运作方式,这个类在连接数据库之前会执行determineCurrentLookupKey()方法,这个方法返回的数据将作为key去targetDataSources中查找相应的值,如果查找到相对应的DataSource,那么就使用此DataSource获取数据库连接。

基本的原理已经说完了,接下来去项目里面配置就OK。

1、创建枚举类DataSourceKey列出你所有的数据源名称,当然了,类名你可以按照自己的取名习惯,下面所有的类也是如此。

public enum DataSourceKey {DB_MASTER,DB_SLAVE1,DB_SLAVE2,DB_OTHER
}

2、创建DynamicDataSourceContextHolder类,这个类是为了解决多线程访问全局变量的问题。

import org.apache.commons.lang3.RandomUtils;
import org.apache.log4j.Logger;/*** @author RocLiu [apedad@qq.com]* @version 1.0*/
public class DynamicDataSourceContextHolder {private static final Logger LOG = Logger.getLogger(DynamicDataSourceContextHolder.class);private static final ThreadLocal<DataSourceKey> currentDatesource = new ThreadLocal<>();/*** 清除当前数据源*/public static void clear() {currentDatesource.remove();}/*** 获取当前使用的数据源** @return 当前使用数据源的ID*/public static DataSourceKey get() {return currentDatesource.get();}/*** 设置当前使用的数据源** @param value 需要设置的数据源ID*/public static void set(DataSourceKey value) {currentDatesource.set(value);}/*** 设置从从库读取数据* 采用简单生成随机数的方式切换不同的从库*/public static void setSlave() {if (RandomUtils.nextInt(0, 2) > 0) {DynamicDataSourceContextHolder.set(DataSourceKey.DB_SLAVE2);} else {DynamicDataSourceContextHolder.set(DataSourceKey.DB_SLAVE1);}}
}

3、创建类DynamicRoutingDataSource继承AbstractRoutingDataSource类并且实现determineCurrentLookupKey()方法,设置数据源。

import org.apache.log4j.Logger;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;public class DynamicRoutingDataSource extends AbstractRoutingDataSource {private static final Logger LOG = Logger.getLogger(DynamicRoutingDataSource.class);@Overrideprotected Object determineCurrentLookupKey() {LOG.info("当前数据源:{}"+ DynamicDataSourceContextHolder.get());return DynamicDataSourceContextHolder.get();}
}

4、配置数据源,这一步比较重要,创建配置类DynamicDataSourceConfiguration

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.apedad.example.commons.DataSourceKey;
import com.apedad.example.commons.DynamicRoutingDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;@MapperScan(basePackages = "com.apedad.example.dao")
@Configuration
public class DynamicDataSourceConfiguration {@Bean@ConfigurationProperties(prefix = "multiple.datasource.master")//此处的"multiple.datasource.master"需要你在application.properties中配置,详细信息看下面贴出的application.properties文件。public DataSource dbMaster() {return DruidDataSourceBuilder.create().build();}@Bean@ConfigurationProperties(prefix = "multiple.datasource.slave1")public DataSource dbSlave1() {return DruidDataSourceBuilder.create().build();}@Bean@ConfigurationProperties(prefix = "multiple.datasource.slave2")public DataSource dbSlave2() {return DruidDataSourceBuilder.create().build();}@Bean@ConfigurationProperties(prefix = "multiple.datasource.other")public DataSource dbOther() {return DruidDataSourceBuilder.create().build();}/*** 核心动态数据源** @return 数据源实例*/@Beanpublic DataSource dynamicDataSource() {DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();dataSource.setDefaultTargetDataSource(dbMaster());Map<Object, Object> dataSourceMap = new HashMap<>(4);dataSourceMap.put(DataSourceKey.DB_MASTER, dbMaster());dataSourceMap.put(DataSourceKey.DB_SLAVE1, dbSlave1());dataSourceMap.put(DataSourceKey.DB_SLAVE2, dbSlave2());dataSourceMap.put(DataSourceKey.DB_OTHER, dbOther());dataSource.setTargetDataSources(dataSourceMap);return dataSource;}@Beanpublic SqlSessionFactory sqlSessionFactory() throws Exception {SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(dynamicDataSource());//此处设置为了解决找不到mapper文件的问题sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));return sqlSessionFactoryBean.getObject();}@Beanpublic SqlSessionTemplate sqlSessionTemplate() throws Exception {return new SqlSessionTemplate(sqlSessionFactory());}/*** 事务管理** @return 事务管理实例*/@Beanpublic PlatformTransactionManager platformTransactionManager() {return new DataSourceTransactionManager(dynamicDataSource());}
}

5、为了不影响业务代码而实现数据源切换,我决定使用AOP切换数据源,为了准确的知道哪个地方需要切换哪个数据源,我这里使用自定义注解的方式,如果你又更好的方式也推荐你使用自己的方式。创建自定义注解类:TargetDataSource:

import com.apedad.example.commons.DataSourceKey;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {DataSourceKey dataSourceKey() default DataSourceKey.DB_MASTER;
}

6、编写数据源切换切面类:DynamicDataSourceAspect

import com.apedad.example.annotation.TargetDataSource;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
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;@Aspect
@Order(-1)
@Component
public class DynamicDataSourceAspect {private static final Logger LOG = Logger.getLogger(DynamicDataSourceAspect.class);@Pointcut("execution(* com.apedad.example.service.*.list*(..))")public void pointCut() {}/*** 执行方法前更换数据源** @param joinPoint        切点* @param targetDataSource 动态数据源*/@Before("@annotation(targetDataSource)")public void doBefore(JoinPoint joinPoint, TargetDataSource targetDataSource) {DataSourceKey dataSourceKey = targetDataSource.dataSourceKey();if (dataSourceKey == DataSourceKey.DB_OTHER) {LOG.info(String.format("设置数据源为  %s", DataSourceKey.DB_OTHER));DynamicDataSourceContextHolder.set(DataSourceKey.DB_OTHER);} else {LOG.info(String.format("使用默认数据源  %s", DataSourceKey.DB_MASTER));DynamicDataSourceContextHolder.set(DataSourceKey.DB_MASTER);}}/*** 执行方法后清除数据源设置** @param joinPoint        切点* @param targetDataSource 动态数据源*/@After("@annotation(targetDataSource)")public void doAfter(JoinPoint joinPoint, TargetDataSource targetDataSource) {LOG.info(String.format("当前数据源  %s  执行清理方法", targetDataSource.dataSourceKey()));DynamicDataSourceContextHolder.clear();}@Before(value = "pointCut()")public void doBeforeWithSlave(JoinPoint joinPoint) {MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();//获取当前切点方法对象Method method = methodSignature.getMethod();if (method.getDeclaringClass().isInterface()) {//判断是否为借口方法try {//获取实际类型的方法对象method = joinPoint.getTarget().getClass().getDeclaredMethod(joinPoint.getSignature().getName(), method.getParameterTypes());} catch (NoSuchMethodException e) {LOG.error("方法不存在!", e);}}if (null == method.getAnnotation(TargetDataSource.class)) {DynamicDataSourceContextHolder.setSlave();}}
}

7、在springboot程序运行入口中设置取消自动配置数据源

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class SpringBootDynamicDatasourceStartedApplication {public static void main(String[] args) {SpringApplication.run(SpringBootDynamicDatasourceStartedApplication.class, args);}
}

8、使用,在你需要切换数据源的service方法上加上注解就OK,注意:如果你使用了接口对service层进行分离,那么注解需要添加到你的实现类的相关方法上。示例如下

@Service("userInfoService")
public class UserInfoServiceImpl implements UserInfoService {private static final Logger LOG = Logger.getLogger(UserInfoServiceImpl.class);@Resourceprivate UserInfoMapper userInfoMapper;@TargetDataSource(dataSourceKey = DataSourceKey.DB_OTHER)@Overridepublic List<UserInfo> listAll() {return userInfoMapper.listAll();}//使用此注解来切换到想切换的数据源@TargetDataSource(dataSourceKey = DataSourceKey.DB_OTHER)@Overridepublic int insert(UserInfo userInfo) {return userInfoMapper.insert(userInfo);}
}

测试用例这里就不赘述了,详情可以参考下面的源码。
项目源码地址:GitHub - Apedad/spring-boot-dynamic-datasource-started: springboot使用mybatis多数据源动态切换的实现

最后,有个问题需知,此处事务只能回滚默认数据源操作,如果需要回滚其他数据源的操作请使用分布式事务进行处理。

springboot使用mybatis多数据源动态切换的实现相关推荐

  1. SpringBoot+AOP实现多数据源动态切换

    SpringBoot+AOP实现多数据源动态切换 背景 设计总体思路 步骤 背景 系统后端需要访问多个数据库,现有的数据库连接配置写入配置文件中.后端需要从一个数据库的配置表里动态的读取其它mysql ...

  2. Spring-Boot + AOP实现多数据源动态切换

    2019独角兽企业重金招聘Python工程师标准>>> 最近在做保证金余额查询优化,在项目启动时候需要把余额全量加载到本地缓存,因为需要全量查询所有骑手的保证金余额,为了不影响主数据 ...

  3. mysql双数据源动态切换_Spring boot+Mybatis多数据源动态切换

    通过重写AbstractRoutingDataSource类中方法determineTargetDataSource,determineCurrentLookupKey,afterProperties ...

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

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

  5. Spring+Mybatis多数据源配置(四)——AbstractRoutingDataSource实现数据源动态切换

    欢迎支持笔者新作:<深入理解Kafka:核心设计与实践原理>和<RabbitMQ实战指南>,同时欢迎关注笔者的微信公众号:朱小厮的博客. 欢迎跳转到本文的原文链接:https: ...

  6. mybatis手动切换数据库_在Spring项目中使用 Mybatis 如何实现动态切换数据源

    在Spring项目中使用 Mybatis 如何实现动态切换数据源 发布时间:2020-11-17 16:20:11 来源:亿速云 阅读:108 作者:Leah 这篇文章将为大家详细讲解有关在Sprin ...

  7. springboot+jpa+mybatis 多数据源支持

    springboot+jpa+mybatis 多数据源支持 配置dataSource import org.springframework.beans.factory.annotation.Quali ...

  8. SpringBoot配置Mybatis多数据源

    SpringBoot配置Mybatis多数据源 配置多数据源可以将springboot自动装配的数据源给关闭. 1.添加pom文件,只需要添加数据源驱动和mybatis包 <dependency ...

  9. Proxool配置多数据源动态切换

    2019独角兽企业重金招聘Python工程师标准>>> 前段时间遇到多数据源动态切换问题,总结一下,做个记录,以备后续之需! 首先附上proxool连接池的配置方法:http://3 ...

最新文章

  1. zTree实现节点修改的实时刷新
  2. 自定义 coding.net 静态网站域名
  3. es python demo
  4. Linux学习第十篇之用户管理命令useradd、passwd、who、w
  5. yarn配置日志聚合:将日志都聚集到某一台服务器
  6. 五阶行列式的对角线之和,函数实现
  7. python 定时器_python 线程之四:定时器(Timer),非阻塞
  8. 互联网日报 | 7月19日 星期一 | 美团外卖成立骑手服务部;金山办公发布“文档中台”;一汽-大众奥迪在华销量突破700万辆...
  9. 不允许指针指向不完整的类类型_8.7 C语言动态内存分配与指向它的指针变量
  10. java.lang.UnsupportedClassVersionError: JVMCFRE003解决方法--jdk 1.6 中switch的参数无法使用String类型
  11. windows GDI开发
  12. 逻辑函数代数法化简(二)
  13. c语言里的pow函数(好理解,易懂)
  14. Html+Css实现鼠标经过图片放大
  15. 《花花公子》刊登1985年乔布斯专访
  16. 【单调栈】P4147 玉蟾宫
  17. Cocos Creator 获得手机陀螺仪(Gyrometer)数据
  18. html DOM------document
  19. 数据库面试题(SQL语句)
  20. HDFS心跳机制--判断DN失联部分的源码解析

热门文章

  1. 操作系统精选习题——第六章
  2. 基于WEB的网上购物系统的设计与实现|电商购物商城
  3. 数据结构之 击鼓传花
  4. 根据国家法定节假日来计算出报告时间
  5. 安装TiDB的软件和硬件环境要求
  6. Element UI for Vue 3.0 来了!
  7. 【NDPI】源码解析之深度包检测分析(一)
  8. 百度地图WebAPI
  9. 64、滑动窗口的最大值
  10. java listbox_将数据从Listbox1复制到Listbox2