【万字长文】SpringBoot整合MyBatis搭建MySQL多数据源完整教程(提供Gitee源码)
前言:在我往期的博客介绍了2种关于如何使用SpringBoot搭建多数据源操作,本期博客我参考的是目前主流的框架,把最后一种整合多数据源的方式以博客的形式讲解完,整合的过程比较传统和复杂,不过我依旧会把每个实体类的思路都给大家讲解清楚的,项目的最后我都会提供Gitee源码地址。
往期博客:
第一种:SpringBoot+Jpa配置Oracle多数据源(提供Gitee源码)
第二种:SpringBoot+Mybatis搭建Oracle多数据源配置简述(提供Gitee源码)
后续补充:
目录
5.1、DynamicDataSourceContextHolder数据源切换处理类
一、导入pom依赖
org.springframework.boot spring-boot-starter-web org.projectlombok lombok true mysql mysql-connector-java 8.0.29 org.mybatis.spring.boot mybatis-spring-boot-starter 2.2.2 com.alibaba druid-spring-boot-starter 1.2.16 org.springframework.boot spring-boot-starter-aop org.springframework.boot spring-boot-starter-test test
二、yml配置文件
# Mybatis配置mybatis: # 配置mapper的扫描,找到所有的mapper.xml映射文件 mapper-locations: classpath:mapper MASTER, SLAVE}
四、Spring工具类
主要作用是提供通过名字获取Bean实例的静态方法。
实现BeanFactoryPostProcessor和ApplicationContextAware接口,在Spring容器初始化时,将ConfigurableListableBeanFactory和ApplicationContext的实例保存在静态变量中。
@Component标记此工具类交给Spring容器托管。
@Componentpublic final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware{ private static ConfigurableListableBeanFactory beanFactory; private static ApplicationContext applicationContext;}
postProcessBeanFactory方法会在Bean定义加载完成但实例化之前执行,这时保存BeanFactory实例。
@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException{ SpringUtils.beanFactory = beanFactory;}
setApplicationContext会在上下文准备完成后执行,这时保存ApplicationContext实例。
@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException{ SpringUtils.applicationContext = applicationContext;}
提供getBean方法,根据名字从静态的BeanFactory中获取Bean实例。
@SuppressWarnings("unchecked")表示去抑制未检查的转型、参数化变量相关的警告。
@SuppressWarnings("unchecked")public static T getBean(String name) throws BeansException{ return (T) beanFactory.getBean(name);}
完整代码:
package com.example.multiple.utils;import org.springframework.beans.BeansException;import org.springframework.beans.factory.config.BeanFactoryPostProcessor;import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Component;@Componentpublic final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware{ private static ConfigurableListableBeanFactory beanFactory; private static ApplicationContext applicationContext; @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { SpringUtils.beanFactory = beanFactory; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringUtils.applicationContext = applicationContext; } @SuppressWarnings("unchecked") public static T getBean(String name) throws BeansException { return (T) beanFactory.getBean(name); }}
五、配置类
先基本介绍一下配置类以及它们之间的关系
1、DynamicDataSourceContextHolder
ThreadLocal持有者,用于存储当前数据源的key。
2、DynamicDataSource
自定义动态数据源,内部持有多个目标数据源Map,可通过设置key动态切换数据源。
3、DruidProperties
用于读取Druid数据源的配置属性,比如最大连接数、最小连接数等。
4、DruidConfig
实现了Druid的多数据源配置,创建了master和slave两个数据源bean,然后通过DynamicDataSource组装成一个动态数据源。
5、DataSource
注解用于标注方法或类上,指定使用哪个数据源。
DataSourceAspect
AOP切面,在方法执行前通过DataSource注解获取数据源key,设置到DynamicDataSourceContextHolder中,以切换到指定数据源。
工作流程是:
1、DruidConfig先创建多个数据源Bean,交给DynamicDataSource进行整合。
2、业务方法通过DataSource注解指定数据源。
DataSourceAspect在方法执行前,读取DataSource注解,获取数据源key,设置到ContextHolder。
4、业务方法调用Mapper接口的方法时,会通过SqlSessionTemplate执行SQL。
5、SqlSessionTemplate内部使用的DataSource是DynamicDataSource。
6、在获取Connection之前,DynamicDataSource会先调用determineCurrentLookupKey方法。
7、determineCurrentLookupKey从ContextHolder获取数据源Key,确定需要使用的目标数据源。
8、DynamicDataSource根据ContextHolder中的key,然后从中获取Connection,路由到对应的数据源执行SQL。
9、之后使用这个Connection执行SQL,完成数据库操作。
这样就实现了基于Druid的多数据源切换,主要是通过AOP+ThreadLocal动态设置数据源key实现的。
5.1、DynamicDataSourceContextHolder数据源切换处理类
定义了一个ThreadLocal类型的CONTEXT_HOLDER,它将为每个线程提供一个独立的副本storage。
private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>();
setDataSourceType方法用于设置当前线程要使用的数据源类型,会把类型存入CONTEXT_HOLDER这个ThreadLocal中。
public static void setDataSourceType(String dsType){ log.info("切换到{}数据源", dsType); CONTEXT_HOLDER.set(dsType);}
getDataSourceType用于获取当前线程所使用的数据源类型,是从CONTEXT_HOLDER这个ThreadLocal中获取。
public static String getDataSourceType(){ return CONTEXT_HOLDER.get();}
clearDataSourceType用于清空当前线程的数据源类型信息。
public static void clearDataSourceType(){ CONTEXT_HOLDER.remove();}
这样使用ThreadLocal就能实现一个线程内部共享这个数据源类型变量,并且每个线程的变量都是独立的。
完整代码:
package com.example.multiple.config.datasource;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class DynamicDataSourceContextHolder{ public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class); private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>(); public static void setDataSourceType(String dsType) { log.info("切换到{}数据源", dsType); CONTEXT_HOLDER.set(dsType); } public static String getDataSourceType() { return CONTEXT_HOLDER.get(); } public static void clearDataSourceType() { CONTEXT_HOLDER.remove(); }}
5.2、DynamicDataSource动态数据源路由类
这个DynamicDataSource类继承了AbstractRoutingDataSource,实现了一个动态切换数据源的路由Datasource。
public class DynamicDataSource extends AbstractRoutingDataSource{}
构造方法中调用父类的方法设置默认数据源和所有目标数据源Map。
public DynamicDataSource(DataSource defaultTargetDataSource, Map
实现了determineCurrentLookupKey方法,在此方法中,通过DynamicDataSourceContextHolder工具类获取当前线程上的数据源类型,然后将当前Lookup Key设置为这个数据源类型。
@Overrideprotected Object determineCurrentLookupKey(){ return DynamicDataSourceContextHolder.getDataSourceType();}
最后,AbstractRoutingDataSource会根据这个Lookup Key,在目标数据源Map中查找对应的DataSource,作为获取连接的源。这样通过determineCurrentLookupKey的实现,动态返回当前线程上的DataSource类型。配合DynamicDataSourceContextHolder来切换设置线程DataSource类型。就能根据线程运行时的数据源类型,动态切换到不同的数据源上获取连接。实现了根据当前运行情况动态切换多个数据源的功能。
完整代码:
package com.example.multiple.config.datasource;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import javax.sql.DataSource;import java.util.Map;public class DynamicDataSource extends AbstractRoutingDataSource{ public DynamicDataSource(DataSource defaultTargetDataSource, Map
5.3、DruidProperties配置属性
从配置中加载属性,并设置到DruidDataSource中,创建一个可用的DataSource实例,代码上都有注释,这边就不多做讲解了。
完整代码:
package com.example.multiple.config.properties;import com.alibaba.druid.pool.DruidDataSource;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Configuration;@Configurationpublic class DruidProperties{ @Value("${spring.datasource.druid.initialSize}") private int initialSize; @Value("${spring.datasource.druid.minIdle}") private int minIdle; @Value("${spring.datasource.druid.maxActive}") private int maxActive; @Value("${spring.datasource.druid.maxWait}") private int maxWait; @Value("${spring.datasource.druid.connectTimeout}") private int connectTimeout; @Value("${spring.datasource.druid.socketTimeout}") private int socketTimeout; @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}") private int timeBetweenEvictionRunsMillis; @Value("${spring.datasource.druid.minEvictableIdleTimeMillis}") private int minEvictableIdleTimeMillis; @Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}") private int maxEvictableIdleTimeMillis; @Value("${spring.datasource.druid.validationQuery}") private String validationQuery; @Value("${spring.datasource.druid.testWhileIdle}") private boolean testWhileIdle; @Value("${spring.datasource.druid.testOnBorrow}") private boolean testOnBorrow; @Value("${spring.datasource.druid.testOnReturn}") private boolean testOnReturn; public DruidDataSource dataSource(DruidDataSource datasource) { datasource.setInitialSize(initialSize); datasource.setMaxActive(maxActive); datasource.setMinIdle(minIdle); datasource.setMaxWait(maxWait); datasource.setConnectTimeout(connectTimeout); datasource.setSocketTimeout(socketTimeout); datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis); datasource.setValidationQuery(validationQuery); datasource.setTestWhileIdle(testWhileIdle); datasource.setTestOnBorrow(testOnBorrow); datasource.setTestOnReturn(testOnReturn); return datasource; }}
5.4、DruidConfig多数据源核心配置类
1、使用@Configuration标记此类为核心配置类。
@Configurationpublic class DruidConfig{}
注册一个创建master主数据源的Bean
第一步、@ConfigurationProperties注解加载名称为“spring.datasource.druid.master”的属性配置。
第二步、通过DruidDataSourceBuilder创建一个DruidDataSource实例。
第三步、将DruidDataSource实例传入DruidProperties的dataSource方法中。
第四步、DruidProperties会根据加载的属性配置,设置DruidDataSource的各种属性,如最大连接数,最小连接数等。
第五步、dataSource方法会返回设置好属性的DruidDataSource实例。
最后这个配置好的DruidDataSource将作为masterDataSource Bean的实例,所以它实现了使用Spring Boot的属性配置方式,加载druid.master的配置,并设置到DruidDataSource中,创建一个可用的master数据源Bean。
@Bean@ConfigurationProperties("spring.datasource.druid.master")public DataSource masterDataSource(DruidProperties druidProperties){ DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); return druidProperties.dataSource(dataSource);}
注册一个创建salve从数据源的Bean,整体步骤和上面类似,不多做阐述。
@ConditionalOnProperty注解的作用是,根据给定的条件来决定这个Bean是否创建。只有当配置了spring.datasource.druid.slave.enabled=true时,这个slaveDataSource的Bean才会被创建。
@Bean@ConfigurationProperties("spring.datasource.druid.slave")@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")public DataSource slaveDataSource(DruidProperties druidProperties){ DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); return druidProperties.dataSource(dataSource);}
设置数据源的方法setDataSource
第一步、方法接收一个Map对象targetDataSources,数据源名称sourceName和数据源Bean名称beanName作为参数。
第二步、从Spring容器中通过SpringUtils工具类获取beanName对应的DataSource Bean实例。
最后、将得到的DataSource实例根据sourceNamekey,存入targetDataSources这个Map中。
public void setDataSource(Map
这样通过beanName加载DataSource,并使用自定义的sourceName作为key存储到targetDataSources中。目标是构建一个自定义名称与数据源实例的映射关系,存在targetDataSources这个容器中。这可以实现多数据源的配置管理,通过不同的sourceName获取对应的数据源实例。
实现动态数据源的配置
第一步、创建一个Map用于存放目标数据源,并将名为masterDataSource的Bean作为主数据源放入Map。
第二步、调用setDataSource方法将名为slaveDataSource的Bean放入Map,Key设置为SLAVE。
最后、使用主数据源实例和数据源Map创建DynamicDataSource实例,并设置为Primary,即默认的数据源。
@Bean(name = "dynamicDataSource")@Primarypublic DynamicDataSource dataSource(DataSource masterDataSource){ Map
完整代码:
package com.example.multiple.config;import java.util.HashMap;import java.util.Map;import javax.sql.DataSource;import com.example.multiple.config.datasource.DynamicDataSource;import com.example.multiple.enums.DataSourceType;import com.example.multiple.config.properties.DruidProperties;import com.example.multiple.utils.SpringUtils;import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import com.alibaba.druid.pool.DruidDataSource;import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;@Configurationpublic class DruidConfig{ @Bean @ConfigurationProperties("spring.datasource.druid.master") public DataSource masterDataSource(DruidProperties druidProperties) { DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); return druidProperties.dataSource(dataSource); } @Bean @ConfigurationProperties("spring.datasource.druid.slave") @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true") public DataSource slaveDataSource(DruidProperties druidProperties) { DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); return druidProperties.dataSource(dataSource); } @Bean(name = "dynamicDataSource") @Primary public DynamicDataSource dataSource(DataSource masterDataSource) { Map
六、DataSource自定义多数据源切换注解
@Target和@Retention表示该注解可以用于方法和类上,并且可以保留到运行时。
@Documented表示该注解会包含在javadoc中。
@Inherited表示该注解可以被子类继承。
注解只有一个value属性,类型是DataSourceType枚举,默认值为MASTER。
@Target({ ElementType.METHOD, ElementType.TYPE })@Retention(RetentionPolicy.RUNTIME)@Documented@Inheritedpublic @interface DataSource{ public DataSourceType value() default DataSourceType.MASTER;}
七、DataSourceAspect动态数据源的切面类
@Aspect标记此类为切面类、@Order指定Bean的加载顺序、@Component标记此类交给Spring容器托管。
@Aspect@Order(1)@Componentpublic class DataSourceAspect{}
@Pointcut定义了切点,这里是匹配所有@DataSource注解的方法或类。
@Pointcut("@annotation(com.example.multiple.annotation.DataSource)" + "|| @within(com.example.multiple.annotation.DataSource)")public void dsPointCut(){}
获取需要切换的数据源
第一步、通过point.getSignature()获取方法签名,并转成MethodSignature类型。
第二步、调用AnnotationUtils的findAnnotation方法,以方法为目标,获取其上的@DataSource注解。
第三步、如果注解不为空,直接返回该注解。
最后、如果方法上没有注解,则以方法所在类为目标再次查找@DataSource注解,并返回。
public DataSource getDataSource(ProceedingJoinPoint point){ MethodSignature signature = (MethodSignature) point.getSignature(); DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class); if (Objects.nonNull(dataSource)) { return dataSource; } return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);}
@Around定义了切点的处理逻辑为环绕增强
第一步、调用getDataSource方法获取目标方法需要的DataSource注解。
第二步、判断如果注解不为空,则调用DynamicDataSourceContextHolder的setDataSourceType方法,将注解value的值(数据源类型)设置到其中。
第三步、调用ProceedingJoinPoint的proceed方法,执行目标方法。
最后、在finally中,调用DynamicDataSourceContextHolder的clearDataSourceType方法,清空线程本地的DataSourceType。
@Around("dsPointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { DataSource dataSource = getDataSource(point); if (dataSource != null) { DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name()); } try { return point.proceed(); } finally { // 销毁数据源 在执行方法之后 DynamicDataSourceContextHolder.clearDataSourceType(); } }
完整代码:
package com.example.multiple.aspectj;import com.example.multiple.annotation.DataSource;import com.example.multiple.config.datasource.DynamicDataSourceContextHolder;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.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.core.annotation.AnnotationUtils;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;import java.util.Objects;@Aspect@Order(1)@Componentpublic class DataSourceAspect{ protected Logger logger = LoggerFactory.getLogger(getClass()); @Pointcut("@annotation(com.example.multiple.annotation.DataSource)" + "|| @within(com.example.multiple.annotation.DataSource)") public void dsPointCut() { } @Around("dsPointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { DataSource dataSource = getDataSource(point); if (dataSource != null) { DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name()); } try { return point.proceed(); } finally { // 销毁数据源 在执行方法之后 DynamicDataSourceContextHolder.clearDataSourceType(); } } public DataSource getDataSource(ProceedingJoinPoint point) { MethodSignature signature = (MethodSignature) point.getSignature(); DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class); if (Objects.nonNull(dataSource)) { return dataSource; } return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class); }}
八、项目完整截图
以上就把多数据源的核心配置讲解完毕了,剩下的就是一些常规整合MyBatis的操作了,我都放在码云上了,这边就不过多写了,完整的项目建包就这样。
九、使用方法
在Mapper层或者Service层上使用@DataSource自定义注解切换到指定数据源即可。
@Mapper@DataSource(DataSourceType.SLAVE)public interface SlaveMapper { public List select();}
运行结果如下:
十、Gitee源码
在yml文件配置好自己的主从数据源一键启动项目即可
项目地址:SpringBoot整合MyBatis搭建MySQL多数据源
十一、总结
以上就是我对于SpringBoot如何整合多数据源的技术分析,也是比较传统化的方式,比较复杂,如有问题欢迎评论区讨论!
来源地址:https://blog.csdn.net/HJW_233/article/details/132032716
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341