网站建设哪家效益快,手游推广平台有哪些,进入微信公众号登录入口,商城网站开发需要哪些人员前言#xff1a;在我往期的博客介绍了2种关于如何使用SpringBoot搭建多数据源操作#xff0c;本期博客我参考的是目前主流的框架#xff0c;把最后一种整合多数据源的方式以博客的形式讲解完#xff0c;整合的过程比较传统和复杂#xff0c;不过我依旧会把每个实体类的思路… 前言在我往期的博客介绍了2种关于如何使用SpringBoot搭建多数据源操作本期博客我参考的是目前主流的框架把最后一种整合多数据源的方式以博客的形式讲解完整合的过程比较传统和复杂不过我依旧会把每个实体类的思路都给大家讲解清楚的项目的最后我都会提供Gitee源码地址。 往期博客 第一种SpringBootJpa配置Oracle多数据源提供Gitee源码 第二种SpringBootMybatis搭建Oracle多数据源配置简述提供Gitee源码 目录
一、导入pom依赖
二、yml配置文件
三、数据源枚举类
四、Spring工具类
五、配置类
5.1、数据源切换处理类
5.2、动态数据源路由类
5.3、Druid配置属性
5.4、多数据源核心配置类
六、自定义多数据源切换注解
七、动态数据源的切面类
八、项目完整截图
九、使用方法
十、Gitee源码
十一、总结 一、导入pom依赖
dependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!-- Lombok驱动依赖 --dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional/dependency!-- MySQL驱动依赖 --dependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion8.0.29/version/dependency!--Mybatis依赖--dependencygroupIdorg.mybatis.spring.boot/groupIdartifactIdmybatis-spring-boot-starter/artifactIdversion2.2.2/version/dependency!-- 阿里数据库连接池 --dependencygroupIdcom.alibaba/groupIdartifactIddruid-spring-boot-starter/artifactIdversion1.2.16/version/dependency!--aop依赖--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency/dependencies
二、yml配置文件
# Mybatis配置
mybatis:# 配置mapper的扫描找到所有的mapper.xml映射文件mapper-locations: classpath:mapper/*/*.xml# 数据源配置
spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriverClassName: com.mysql.cj.jdbc.Driverdruid:# 主库数据源master:url: jdbc:mysql://localhost:3306/master?useUnicodetruecharacterEncodingutf8zeroDateTimeBehaviorconvertToNulluseSSLtrueserverTimezoneGMT%2B8username:password: # 从库数据源slave:# 从数据源开关/默认关闭enabled: trueurl: jdbc:mysql://localhost:3306/slave?useUnicodetruecharacterEncodingutf8zeroDateTimeBehaviorconvertToNulluseSSLtrueserverTimezoneGMT%2B8username:password:# 初始连接数initialSize: 5# 最小连接池数量minIdle: 10# 最大连接池数量maxActive: 20# 配置获取连接等待超时的时间maxWait: 60000# 配置连接超时时间connectTimeout: 30000# 配置网络超时时间socketTimeout: 60000# 配置间隔多久才进行一次检测检测需要关闭的空闲连接单位是毫秒timeBetweenEvictionRunsMillis: 60000# 配置一个连接在池中最小生存的时间单位是毫秒minEvictableIdleTimeMillis: 300000# 配置一个连接在池中最大生存的时间单位是毫秒maxEvictableIdleTimeMillis: 900000# 配置检测连接是否有效validationQuery: SELECT 1 FROM DUALtestWhileIdle: truetestOnBorrow: falsetestOnReturn: falsewebStatFilter:enabled: truestatViewServlet:enabled: true# 设置白名单不填则允许所有访问allow:url-pattern: /druid/*filter:stat:enabled: true# 慢SQL记录log-slow-sql: trueslow-sql-millis: 1000merge-sql: truewall:config:multi-statement-allow: true三、数据源枚举类
public enum DataSourceType
{/*** 主库*/MASTER,/*** 从库*/SLAVE
}
四、Spring工具类
主要作用是提供通过名字获取Bean实例的静态方法。
1、实现BeanFactoryPostProcessor和ApplicationContextAware接口在Spring容器初始化时将ConfigurableListableBeanFactory和ApplicationContext的实例保存在静态变量中。
Component标记此工具类交给Spring容器托管。
Component
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware{/** Spring应用上下文环境 */private static ConfigurableListableBeanFactory beanFactory;private static ApplicationContext applicationContext;}
2、postProcessBeanFactory方法会在Bean定义加载完成但实例化之前执行这时保存BeanFactory实例。
Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
{SpringUtils.beanFactory beanFactory;
}
3、setApplicationContext会在上下文准备完成后执行这时保存ApplicationContext实例。
Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{SpringUtils.applicationContext applicationContext;
}
4、 提供getBean方法,根据名字从静态的BeanFactory中获取Bean实例。
SuppressWarnings(unchecked)表示去抑制未检查的转型、参数化变量相关的警告。
SuppressWarnings(unchecked)
public static T 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;/*** spring工具类 方便在非spring管理环境中获取bean*/
Component
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware
{/** Spring应用上下文环境 */private static ConfigurableListableBeanFactory beanFactory;private static ApplicationContext applicationContext;Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException{SpringUtils.beanFactory beanFactory;}Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException{SpringUtils.applicationContext applicationContext;}/*** 获取对象** param name* return Object 一个以所给名字注册的bean的实例* throws BeansException**/SuppressWarnings(unchecked)public static T T getBean(String name) throws BeansException{return (T) beanFactory.getBean(name);}}五、配置类
5.1、数据源切换处理类
1、定义了一个ThreadLocal类型的CONTEXT_HOLDER它将为每个线程提供一个独立的副本storage。
private static final ThreadLocalString CONTEXT_HOLDER new ThreadLocal();
2、setDataSourceType方法用于设置当前线程要使用的数据源类型会把类型存入CONTEXT_HOLDER这个ThreadLocal中。
public static void setDataSourceType(String dsType)
{log.info(切换到{}数据源, dsType);CONTEXT_HOLDER.set(dsType);
}
3、getDataSourceType用于获取当前线程所使用的数据源类型是从CONTEXT_HOLDER这个ThreadLocal中获取。
public static String getDataSourceType()
{return CONTEXT_HOLDER.get();
}
4、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);/*** 使用ThreadLocal维护变量ThreadLocal为每个使用该变量的线程提供独立的变量副本* 所以每一个线程都可以独立地改变自己的副本而不会影响其它线程所对应的副本。*/private static final ThreadLocalString 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、动态数据源路由类
1、这个DynamicDataSource类继承了AbstractRoutingDataSource实现了一个动态切换数据源的路由Datasource。
public class DynamicDataSource extends AbstractRoutingDataSource{}
2、构造方法中调用父类的方法设置默认数据源和所有目标数据源Map。
public DynamicDataSource(DataSource defaultTargetDataSource, MapObject, Object targetDataSources)
{super.setDefaultTargetDataSource(defaultTargetDataSource);super.setTargetDataSources(targetDataSources);super.afterPropertiesSet();
}
3、实现了determineCurrentLookupKey方法在此方法中通过DynamicDataSourceContextHolder工具类获取当前线程上的数据源类型然后将当前Lookup Key设置为这个数据源类型。
Override
protected 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, MapObject, Object targetDataSources){super.setDefaultTargetDataSource(defaultTargetDataSource);super.setTargetDataSources(targetDataSources);super.afterPropertiesSet();}Overrideprotected Object determineCurrentLookupKey(){return DynamicDataSourceContextHolder.getDataSourceType();}
}5.3、Druid配置属性
从配置中加载属性并设置到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;/*** druid 配置属性**/
Configuration
public 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);/*** 用来检测连接是否有效的sql要求是一个查询语句常用select x。如果validationQuery为nulltestOnBorrow、testOnReturn、testWhileIdle都不会起作用。*/datasource.setValidationQuery(validationQuery);/** 建议配置为true不影响性能并且保证安全性。申请连接的时候检测如果空闲时间大于timeBetweenEvictionRunsMillis执行validationQuery检测连接是否有效。 */datasource.setTestWhileIdle(testWhileIdle);/** 申请连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。 */datasource.setTestOnBorrow(testOnBorrow);/** 归还连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。 */datasource.setTestOnReturn(testOnReturn);return datasource;}
}5.4、多数据源核心配置类 1、使用Configuration标记此类为核心配置类。
Configuration
public class DruidConfig{
}
2、注册一个创建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);
}
3、注册一个创建salve从数据源的Bean整体步骤和上面类似不多做阐述。
ConditionalOnProperty注解的作用是根据给定的条件来决定这个Bean是否创建。只有当配置了spring.datasource.druid.slave.enabledtrue时这个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);
}
4、设置数据源的方法setDataSource
第一步、方法接收一个Map对象targetDataSources数据源名称sourceName和数据源Bean名称beanName作为参数。
第二步、从Spring容器中通过SpringUtils工具类获取beanName对应的DataSource Bean实例。
最后、将得到的DataSource实例根据sourceNamekey存入targetDataSources这个Map中。
public void setDataSource(MapObject, Object targetDataSources, String sourceName, String beanName){try{DataSource dataSource SpringUtils.getBean(beanName);targetDataSources.put(sourceName, dataSource);}catch (Exception e){e.printStackTrace();}}
这样通过beanName加载DataSource并使用自定义的sourceName作为key存储到targetDataSources中。目标是构建一个自定义名称与数据源实例的映射关系存在targetDataSources这个容器中。这可以实现多数据源的配置管理通过不同的sourceName获取对应的数据源实例。
5.5、实现动态数据源的配置
第一步、创建一个Map用于存放目标数据源并将名为masterDataSource的Bean作为主数据源放入Map。
第二步、调用setDataSource方法将名为slaveDataSource的Bean放入MapKey设置为SLAVE。
最后、使用主数据源实例和数据源Map创建DynamicDataSource实例并设置为Primary即默认的数据源。
Bean(name dynamicDataSource)
Primary
public DynamicDataSource dataSource(DataSource masterDataSource)
{MapObject, Object targetDataSources new HashMap();targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);setDataSource(targetDataSources, DataSourceType.SLAVE.name(), slaveDataSource);return new DynamicDataSource(masterDataSource, targetDataSources);
}
完整代码
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;/*** druid 配置多数据源*/
Configuration
public class DruidConfig
{BeanConfigurationProperties(spring.datasource.druid.master)public DataSource masterDataSource(DruidProperties druidProperties){DruidDataSource dataSource DruidDataSourceBuilder.create().build();return druidProperties.dataSource(dataSource);}BeanConfigurationProperties(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)Primarypublic DynamicDataSource dataSource(DataSource masterDataSource){MapObject, Object targetDataSources new HashMap();targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);setDataSource(targetDataSources, DataSourceType.SLAVE.name(), slaveDataSource);return new DynamicDataSource(masterDataSource, targetDataSources);}/*** 设置数据源** param targetDataSources 备选数据源集合* param sourceName 数据源名称* param beanName bean名称*/public void setDataSource(MapObject, Object targetDataSources, String sourceName, String beanName){try{DataSource dataSource SpringUtils.getBean(beanName);targetDataSources.put(sourceName, dataSource);}catch (Exception e){e.printStackTrace();}}}
六、自定义多数据源切换注解
1、Target和Retention表示该注解可以用于方法和类上并且可以保留到运行时。
2、Documented表示该注解会包含在javadoc中。
3、Inherited表示该注解可以被子类继承。
注解只有一个value属性类型是DataSourceType枚举默认值为MASTER。
Target({ ElementType.METHOD, ElementType.TYPE })
Retention(RetentionPolicy.RUNTIME)
Documented
Inherited
public interface DataSource
{/*** 切换数据源名称*/public DataSourceType value() default DataSourceType.MASTER;
}
七、动态数据源的切面类
1、Aspect标记此类为切面类、Order指定Bean的加载顺序、Component标记此类交给Spring容器托管。
Aspect
Order(1)
Component
public class DataSourceAspect{
}
2、Pointcut定义了切点这里是匹配所有DataSource注解的方法或类。
Pointcut(annotation(com.example.multiple.annotation.DataSource) || within(com.example.multiple.annotation.DataSource))
public void dsPointCut()
{
}
3、获取需要切换的数据源
第一步、通过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);
}
4、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)
Component
public 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 ListLogger select();}
运行结果如下 十、Gitee源码
在yml文件配置好自己的主从数据源一键启动项目即可
项目地址SpringBoot整合MyBatis搭建MySQL多数据源
十一、总结
以上就是我对于SpringBoot如何整合多数据源的技术分析也是比较传统化的方式比较复杂如有问题欢迎评论区讨论