配置多重数据源,我这边配置四个数据源,三个连接MySQL但是不同库,另一个连接pgsql
话不多说,看下面
1.首先你得创建一个枚举类DataSourceType ,这个类里面放你要配置的数据源的名字,如下
/** * 多数据源处理 * * @Author w'z'y */ public enum DataSourceType {/** * 主库第一个库 */ MASTER, /** * 第二个库 */ SLAVE, /** * 第三种库 */ SON, /** * 第四个库,连接PGsql 库 */ PGSOURCE }
2.当然你还得引入依赖,因为我上面配置四个数据源,前三个都是MySQL的第四个是连接的pg库,所以要导入MySQL依赖和postgresql,至于里面还有一些读取nacos配置等依赖我就不写出来了
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.18</version> </dependency><dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.2.2</version> </dependency>
3.然后这只是第一步,做完之后你在nacos配置里面把你四个数据源配置一下
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource # 注意这里是自定义的配置,通过 JdbcParamConfig 来加载配置到 Spring 中 # 然后由 DruidConfig 来配置数据源 druid: # 主库数据源 master: driverClassName: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/test1?characterEncoding=UTF-8&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true username: root password: root slave: # 从数据源开关/默认关闭 enabled: true username: root password: root url: jdbc:mysql://127.0.0.1:3306/test2?characterEncoding=UTF-8&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true driverClassName: com.mysql.cj.jdbc.Driver son: # 从数据源开关/默认关闭 enabled: true username: root password: root url: jdbc:mysql://127.0.0.1:3306/test3?characterEncoding=UTF-8&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true driverClassName: com.mysql.cj.jdbc.Driver # 从库数据源 pgsource: # 从数据源开关/默认关闭 enabled: true username: postgres password: postgres url: jdbc:postgresql://127.0.0.1:5432/test4?ssl=false driverClassName: org.postgresql.Driver type: com.zaxxer.hikari.HikariDataSource # 初始连接数 initialSize: 5 # 最小连接池数量 minIdle: 10 # 最大连接池数量 maxActive: 20 # 配置获取连接等待超时的时间 maxWait: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 timeBetweenEvictionRunsMillis: 60000 # 配置一个连接在池中最小生存的时间,单位是毫秒 minEvictableIdleTimeMillis: 300000 # 配置一个连接在池中最大生存的时间,单位是毫秒 maxEvictableIdleTimeMillis: 900000 # 配置检测连接是否有效 validationQuery: SELECT 1
4.在写一个类DruidProperties用来设置druid 从nacos读取配置属性
import com.alibaba.druid.pool.DruidDataSource; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; /** * druid 配置属性 * * @Author w'z'y */ @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.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.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */ datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis); /** * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 */ datasource.setValidationQuery(validationQuery); /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */ datasource.setTestWhileIdle(testWhileIdle); /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */ datasource.setTestOnBorrow(testOnBorrow); /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */ datasource.setTestOnReturn(testOnReturn); return datasource; } }
5.创建一个类DynamicDataSource 继承AbstractRoutingDataSource,配置实现动态数据源
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import javax.sql.DataSource; import java.util.Map; /** * 动态数据源 * * @Author w'z'y */ public class DynamicDataSource extends AbstractRoutingDataSource{ public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources){ super.setDefaultTargetDataSource(defaultTargetDataSource); super.setTargetDataSources(targetDataSources); super.afterPropertiesSet(); } @Override protected Object determineCurrentLookupKey(){ return DynamicDataSourceContextHolder.getDataSourceType(); } }
6.创建类DruidConfig,配置数据源实例,从nacos读取数据源配置
import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties; import com.alibaba.druid.util.Utils; import com.dataapi.core.config.properties.DruidProperties; import com.dataapi.core.datasource.DynamicDataSource; import com.dataapi.core.enums.DataSourceType; import com.dataapi.core.utils.SpringUtils; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import javax.servlet.*; import javax.sql.DataSource; import java.io.IOException; import java.util.HashMap; import java.util.Map; /** * druid 配置多数据源 * * @Author w'z'y */ @Configuration public 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 @ConfigurationProperties("spring.datasource.druid.son") @ConditionalOnProperty(prefix = "spring.datasource.druid.son", name = "enabled", havingValue = "true") public DataSource sonDataSource(DruidProperties druidProperties) { DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); return druidProperties.dataSource(dataSource); } @Bean @ConfigurationProperties("spring.datasource.druid.pgsource") @ConditionalOnProperty(prefix = "spring.datasource.druid.pgsource", name = "enabled", havingValue = "true") public DataSource pgsourceDataSource(DruidProperties druidProperties) { DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); return druidProperties.dataSource(dataSource); } @Bean(name = "dynamicDataSource") @Primary public DynamicDataSource dataSource(DataSource masterDataSource) { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource); setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource"); setDataSource(targetDataSources, DataSourceType.SON.name(), "sonDataSource"); setDataSource(targetDataSources, DataSourceType.PGSOURCE.name(), "pgsourceDataSource"); return new DynamicDataSource(masterDataSource, targetDataSources); } /** * 设置数据源 * * @param targetDataSources 备选数据源集合 * @param sourceName 数据源名称 * @param beanName bean名称 */ public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName) { try { DataSource dataSource = SpringUtils.getBean(beanName); targetDataSources.put(sourceName, dataSource); } catch (Exception e) { } } /** * 去除监控页面底部的广告 */ @SuppressWarnings({ "rawtypes", "unchecked" }) @Bean @ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true") public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties) { // 获取web监控页面的参数 DruidStatProperties.StatViewServlet config = properties.getStatViewServlet(); // 提取common.js的配置路径 String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*"; String commonJsPattern = pattern.replaceAll("\\*", "js/common.js"); final String filePath = "support/http/resources/js/common.js"; // 创建filter进行过滤 Filter filter = new Filter() { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { chain.doFilter(request, response); // 重置缓冲区,响应头不会被重置 response.resetBuffer(); // 获取common.js String text = Utils.readFromResource(filePath); // 正则替换banner, 除去底部的广告信息 text = text.replaceAll("<a.*?banner\"></a><br/>", ""); text = text.replaceAll("powered.*?shrek.wang</a>", ""); response.getWriter().write(text); } @Override public void destroy() { } }; FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(filter); registrationBean.addUrlPatterns(commonJsPattern); return registrationBean; } }
这里我写了一个spring工具类,方便使用
import com.zhdl.util.StringUtils; import org.springframework.aop.framework.AopContext; import org.springframework.beans.BeansException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; 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 * * @Author w'z'y */ @Component public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware{ /** Spring应用上下文环境 */ 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; } /** * 获取对象 * * @param name * @return Object 一个以所给名字注册的bean的实例 * @throws BeansException * */ @SuppressWarnings("unchecked") public static <T> T getBean(String name) throws BeansException{ return (T) beanFactory.getBean(name); } /** * 获取类型为requiredType的对象 * * @param clz * @return * @throws BeansException * */ public static <T> T getBean(Class<T> clz) throws BeansException { T result = (T) beanFactory.getBean(clz); return result; } /** * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true * * @param name * @return boolean */ public static boolean containsBean(String name){ return beanFactory.containsBean(name); } /** * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) * * @param name * @return boolean * @throws NoSuchBeanDefinitionException * */ public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException{ return beanFactory.isSingleton(name); } /** * @param name * @return Class 注册对象的类型 * @throws NoSuchBeanDefinitionException * */ public static Class<?> getType(String name) throws NoSuchBeanDefinitionException { return beanFactory.getType(name); } /** * 如果给定的bean名字在bean定义中有别名,则返回这些别名 * * @param name * @return * @throws NoSuchBeanDefinitionException * */ public static String[] getAliases(String name) throws NoSuchBeanDefinitionException{ return beanFactory.getAliases(name); } /** * 获取aop代理对象 * * @param invoker * @return */ @SuppressWarnings("unchecked") public static <T> T getAopProxy(T invoker){ return (T) AopContext.currentProxy(); } /** * 获取当前的环境配置,无配置返回null * * @return 当前的环境配置 */ public static String[] getActiveProfiles(){ return applicationContext.getEnvironment().getActiveProfiles(); } /** * 获取当前的环境配置,当有多个环境配置时,只获取第一个 * * @return 当前的环境配置 */ public static String getActiveProfile() { final String[] activeProfiles = getActiveProfiles(); return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null; } }
7.然后编写切面DataSourceAspect,对多数据源做处理
import com.zhdl.util.StringUtils; 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; /** * 多数据源处理 * * @Author w'z'y */ @Aspect @Order(1) @Component public class DataSourceAspect{ protected Logger logger = LoggerFactory.getLogger(getClass()); @Pointcut("@annotation(DataSource)" + "|| @within(DataSource)") public void dsPointCut() { } @Around("dsPointCut()") public Object around(ProceedingJoinPoint point) throws Throwable{ DataSource dataSource = getDataSource(point); if (StringUtils.isNotNull(dataSource)){ 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); } }
8.创建类DynamicDataSourceContextHolder,使用ThreadLocal为线程保存线程变量,对数据源切换做处理
import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 数据源切换处理 * * @Author w'z'y */ public class DynamicDataSourceContextHolder{ public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class); /** * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本, * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 */ private static final ThreadLocal<String> 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(); } }
9.写一个注解类DataSource,自定义多数据源切换注解
import java.lang.annotation.*; /** * 自定义多数据源切换注解 * * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准 * * @Author w'z'y */ @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface DataSource{ /** * 切换数据源名称,设置默认库 */ public DataSourceType value() default DataSourceType.MASTER; }
10.之后就可以使用啦
一种是在mapper的方法上面加注解@DataSource(),那这样就是方法查询的就是你的注解里面的库,
如果是在mapper 类上加注解,那整个里面的方法就是查询这个注解里面的库
例如@DataSource(DataSourceType.SLAVE)
查询nacos里面SLAVE里面配置的数据库
在注解里面就是你枚举类里面写的库名,要连接哪个库,你就写哪个库名
如果不加注解那么就会默认使用注解类DataSource里面设置的默认的库
@Mapper @DataSource(DataSourceType.MASTER) public interface CurveDataMapper extends BaseMapper<TOrgCurve> { Teacher getName(@Param("year")String year); @DataSource(DataSourceType.SLAVE) Student getName(@Param("year")String year); }
第二种写法,在代码逻辑里面,如果你调用Mybatis-plus的方法,则你就切换数据源,
DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.SLAVE.name());
setDataSourceType()里面写你切换数据源的在枚举类里面的名字
如下所示
DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.SLAVE.name()); Student student = studentMapper.selectById("12345");
创作不易,给个关注不迷路