springboot使用mybatis多数据源动态切换的实现
需求:项目使用了读写分离,或者数据进行了分库处理,我们希望在操作不同的数据库的时候,我们的程序能够动态的切换到相应的数据库,执行相关的操作。
首先,你需要一个能够正常运行的springboot项目,配置mybatis并且能够正常的操作数据库(增删查改)
现在开始实现:
思路:现在项目的结构设计基本上是基于MVC的,那么数据库的操作集中在dao层完成,主要业务逻辑在service层处理,controller层处理请求。假设在执行dao层代码之前能够将数据源(DataSource)换成我们想要执行操作的数据源,那么这个问题就解决了。
虽然思路是有了,但是怎么换呢?
爬了很多的博客查看了下官方的文档,我找到了这样一个类:AbstractRoutingDataSource,它继承于AbstractDataSource而AbstractDataSource又实现了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多数据源动态切换的实现相关推荐
- SpringBoot+AOP实现多数据源动态切换
SpringBoot+AOP实现多数据源动态切换 背景 设计总体思路 步骤 背景 系统后端需要访问多个数据库,现有的数据库连接配置写入配置文件中.后端需要从一个数据库的配置表里动态的读取其它mysql ...
- Spring-Boot + AOP实现多数据源动态切换
2019独角兽企业重金招聘Python工程师标准>>> 最近在做保证金余额查询优化,在项目启动时候需要把余额全量加载到本地缓存,因为需要全量查询所有骑手的保证金余额,为了不影响主数据 ...
- mysql双数据源动态切换_Spring boot+Mybatis多数据源动态切换
通过重写AbstractRoutingDataSource类中方法determineTargetDataSource,determineCurrentLookupKey,afterProperties ...
- springboot多数据源动态切换和自定义mybatis分页插件
1.配置多数据源 增加druid依赖 完整pom文件 数据源配置文件 route.datasource.driver-class-name= com.mysql.jdbc.Driver route.d ...
- Spring+Mybatis多数据源配置(四)——AbstractRoutingDataSource实现数据源动态切换
欢迎支持笔者新作:<深入理解Kafka:核心设计与实践原理>和<RabbitMQ实战指南>,同时欢迎关注笔者的微信公众号:朱小厮的博客. 欢迎跳转到本文的原文链接:https: ...
- mybatis手动切换数据库_在Spring项目中使用 Mybatis 如何实现动态切换数据源
在Spring项目中使用 Mybatis 如何实现动态切换数据源 发布时间:2020-11-17 16:20:11 来源:亿速云 阅读:108 作者:Leah 这篇文章将为大家详细讲解有关在Sprin ...
- springboot+jpa+mybatis 多数据源支持
springboot+jpa+mybatis 多数据源支持 配置dataSource import org.springframework.beans.factory.annotation.Quali ...
- SpringBoot配置Mybatis多数据源
SpringBoot配置Mybatis多数据源 配置多数据源可以将springboot自动装配的数据源给关闭. 1.添加pom文件,只需要添加数据源驱动和mybatis包 <dependency ...
- Proxool配置多数据源动态切换
2019独角兽企业重金招聘Python工程师标准>>> 前段时间遇到多数据源动态切换问题,总结一下,做个记录,以备后续之需! 首先附上proxool连接池的配置方法:http://3 ...
最新文章
- zTree实现节点修改的实时刷新
- 自定义 coding.net 静态网站域名
- es python demo
- Linux学习第十篇之用户管理命令useradd、passwd、who、w
- yarn配置日志聚合:将日志都聚集到某一台服务器
- 五阶行列式的对角线之和,函数实现
- python 定时器_python 线程之四:定时器(Timer),非阻塞
- 互联网日报 | 7月19日 星期一 | 美团外卖成立骑手服务部;金山办公发布“文档中台”;一汽-大众奥迪在华销量突破700万辆...
- 不允许指针指向不完整的类类型_8.7 C语言动态内存分配与指向它的指针变量
- java.lang.UnsupportedClassVersionError: JVMCFRE003解决方法--jdk 1.6 中switch的参数无法使用String类型
- windows GDI开发
- 逻辑函数代数法化简(二)
- c语言里的pow函数(好理解,易懂)
- Html+Css实现鼠标经过图片放大
- 《花花公子》刊登1985年乔布斯专访
- 【单调栈】P4147 玉蟾宫
- Cocos Creator 获得手机陀螺仪(Gyrometer)数据
- html DOM------document
- 数据库面试题(SQL语句)
- HDFS心跳机制--判断DN失联部分的源码解析