springboot实现多数据源配置(Druid/Hikari)

2023-11-03 13:50

本文主要是介绍springboot实现多数据源配置(Druid/Hikari),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

使用springboot+mybatis-plus+(Druid/Hikari)实现多数据源配置

操作步骤:
  1. 引入相应的maven坐标
  2. 编写mybatis配置,集成mybatis或mybatis-plus(如果已集成可跳过)
  3. 编写数据源配置类
  4. 编写注解,并通过aop进行增强(编写数据源切换代码)
  5. 类或方法中使用注解,对数据源进行切换

第一步:引入需要相应maven坐标

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- aop注解实现aspectjweaver依赖 --><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.6</version></dependency><!-- mysql-plus 依赖 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.3.0</version></dependency><!-- Mysql驱动包 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--阿里数据库连接池 --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.14</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency>

第二步:编写mybatis-plus配置集成

server:port: 8000spring:datasource:driver-class-name: com.mysql.cj.jdbc.Drivermaster:url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=trueusername: rootpassword: 123456slaver:# 从数据源开关/默认关闭enabled: falseurl: jdbc:mysql://localhost:3306/slave?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=trueusername: rootpassword: 123456druid:# 初始连接数initial-size: 5# 最小连接池数量min-idle: 10# 最大连接池数量max-active: 20# 获取连接等待超时时间max-wait: 60000
mybatis-plus:mapper-locations: classpath*:mapper/*Mapper.xmllogging:level:com.example.demo: debug
以下是集成mp相关的代码,不需要可直接跳过!!!(以user表为例)

userMapper

package com.example.demo.datesource.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.example.demo.datesource.domain.User;import java.util.List;public interface UserService extends IService<User> {List<User> getUserList();List<User> getUserListByXml();List<User> testSlave();
}

userMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.datesource.mapper.UserMapper"><select id="getUserListByXml" resultType="com.example.demo.datesource.domain.User">select * from user</select>
</mapper>

userService

package com.example.demo.datesource.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.example.demo.datesource.domain.User;import java.util.List;public interface UserService extends IService<User> {List<User> getUserList();List<User> getUserListByXml();List<User> testSlave();
}

userServiceImpl

package com.example.demo.datesource.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.datesource.annotation.UseDataSource;
import com.example.demo.datesource.domain.User;
import com.example.demo.datesource.enums.DataSourceType;
import com.example.demo.datesource.mapper.UserMapper;
import com.example.demo.datesource.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {@Autowiredprivate UserMapper userMapper;@Overridepublic List<User> getUserList() {return list();}@Overridepublic List<User> getUserListByXml(){return userMapper.getUserListByXml();}@Overridepublic List<User> testSlave() {return list();}
}

User

package com.example.demo.datesource.domain;import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;@Data
@TableName("user")
public class User {@TableIdprivate Long id;private String name;private String sex;private int age;
}
mybatis-plus 集成完毕!

第三步:编写数据源的配置类(例:Druid、Hikari)

一、@ConfigurationProperties要和配置文件中的配置对应上
(1)Druid:
package com.example.demo.datesource.config;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.example.demo.datesource.config.properties.DruidPropertiesConfig;
import com.example.demo.datesource.datasource.DynamicDataSource;
import com.example.demo.datesource.enums.DataSourceType;
import com.example.demo.datesource.utils.SpringUtils;
import org.springframework.beans.factory.annotation.Qualifier;
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 javax.sql.DataSource;
import javax.swing.*;
import java.util.HashMap;
import java.util.Map;@Configuration
public class DruidConfig {@Bean@ConfigurationProperties("spring.datasource.master")public DataSource masterDataSource(DruidPropertiesConfig propertiesConfig){DruidDataSource dataSource = DruidDataSourceBuilder.create().build();return propertiesConfig.setDataSourceProperties(dataSource);}@Bean@ConfigurationProperties("spring.datasource.slaver")@ConditionalOnProperty(prefix = "spring.datasource.slaver", name = "enabled", havingValue = "true")public DataSource slaverDataSource(DruidPropertiesConfig propertiesConfig){DruidDataSource dataSource = DruidDataSourceBuilder.create().build();return propertiesConfig.setDataSourceProperties(dataSource);}@Bean@Primarypublic DynamicDataSource dataSource(DataSource masterDataSource){Map<Object,Object> targetDataSource = new HashMap<>();targetDataSource.put(DataSourceType.MASTER.name(),masterDataSource);setDataSource(targetDataSource,DataSourceType.SLAVE.name(),"slaverDataSource");return new DynamicDataSource(masterDataSource,targetDataSource);}public void setDataSource(Map<Object,Object> targetDataSource,String sourceName,String beanName){try {DataSource dataSource = SpringUtils.getBean(beanName);targetDataSource.put(sourceName,dataSource);}catch (Exception e){}}}

增加DruidPropertiesConfig类,用于给数据源配置上配置文件对应的属性

package com.example.demo.datesource.config.properties;import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;@Configuration
public class DruidPropertiesConfig {@Value("${spring.datasource.druid.initial-size}")private int initialSize;@Value("${spring.datasource.druid.min-idle}")private int minIdle;@Value("${spring.datasource.druid.max-active}")private int maxActive;@Value("${spring.datasource.druid.max-wait}")private int maxWait;public DruidDataSource setDataSourceProperties(DruidDataSource dataSource){dataSource.setInitialSize(this.initialSize);dataSource.setMinIdle(this.minIdle);dataSource.setMaxActive(this.maxActive);dataSource.setMaxWait(this.maxWait);return dataSource;}}
(2)Hikari:配置上基本和druid相同,只需把配置类改为如下且配置文件的“url”改为“jdbc-url”即可

在这里插入图片描述
在这里插入图片描述

二、增加SpringUtils类,用于读取spring中的bean
package com.example.demo.datesource.utils;import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;@Component
public class SpringUtils implements BeanFactoryPostProcessor {private static ConfigurableListableBeanFactory beanFactory;@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {SpringUtils.beanFactory = configurableListableBeanFactory;}public static <T> T getBean(String name){return (T) beanFactory.getBean(name);}}
三、添加DynamicDataSource类

(1)此类是用来配置动态数据源的关键。该类继承spring的抽象类AbstractRoutingDataSource,通过这个类可以实现动态数据源切换。其中维护着默认数据源(defaultTargetDataSource)和数据源列表(targetDataSources),通过afterPropertiesSet()方法对数据源列表进行解析以及设置数据源。
程序每次对数据库发起连接时,都会访问到AbstractRoutingDataSource的getConnection()方法,此方法会调用determineCurrentLookupKey的相应实现,此处实现为获取线程变量。

package com.example.demo.datesource.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<Object, Object> targetDataSources){super.setDefaultTargetDataSource(defaultTargetDataSource);super.setTargetDataSources(targetDataSources);super.afterPropertiesSet();}@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getDataSourceType();}
}

(2)添加DynamicDataSourceContextHolder类,此类用于切换数据源,根据ThreadLocal做多线程数据隔离,每一次切换都能保证不影响其他线程的正常运行

package com.example.demo.datesource.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<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();}/*** 清空数据源变量,此方法可调动gc对线程进行清除*/public static void clearDataSourceType(){CONTEXT_HOLDER.remove();}}

AbstractRoutingDataSource切换线程变量来切换数据源源码。
此处就是获取之前在DynamicDataSourceContextHolder中set到线程中的数据源名,通过数据源名获取维护的数据源列表中对应的数据源

在这里插入图片描述

第四步:使用

大致思路:定义一个切换数据源注解,通过注解aop的形式对数据源进行切换,在类或方法中使用注解,设置对应的数据源名称以此来达到数据源切换

一、定义一个切换数据源注解
package com.example.demo.datesource.annotation;import com.example.demo.datesource.enums.DataSourceType;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UseDataSource {public DataSourceType dataSource() default DataSourceType.MASTER;}
二、编写aop代码

@within:此注解可以作用于类中的所有方法,如果@UseDataSource放到类上,此类下的所有的方法都会被当做切点拦截
@Order(1):提升组件注册到spring中的优先级
point.proceed():原来执行的程序,也就是原请求,想要做环绕增强就再这周围写就可以了

package com.example.demo.datesource.aspectj;import com.example.demo.datesource.annotation.UseDataSource;
import com.example.demo.datesource.datasource.DynamicDataSourceContextHolder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;import javax.sql.DataSource;
import java.util.Objects;@Aspect
@Order(1)
@Component
public class DataSourceAspect {@Pointcut("@annotation(com.example.demo.datesource.annotation.UseDataSource) || @within(com.example.demo.datesource.annotation.UseDataSource)")public void pointCut(){}@Around("pointCut()")public Object around(ProceedingJoinPoint point) throws Throwable{UseDataSource useDataSource = getDataSource(point);if(Objects.nonNull(useDataSource)){DynamicDataSourceContextHolder.setDataSourceType(useDataSource.dataSource().name());}try {return point.proceed();}finally {//清空threadlocalMap的entry。避免内存泄漏DynamicDataSourceContextHolder.clearDataSourceType();}}public UseDataSource getDataSource(ProceedingJoinPoint point){MethodSignature signature = (MethodSignature) point.getSignature();UseDataSource annotation = AnnotationUtils.findAnnotation(signature.getMethod(), UseDataSource.class);if(Objects.nonNull(annotation)){return annotation;}return AnnotationUtils.findAnnotation(signature.getDeclaringType(),UseDataSource.class);}}
三、编写注解到类或方法上

在这里插入图片描述

运行结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

至此,数据源切换成功!!!

另外,如果需要增加新的数据源需要的操作:
1、启动配置文件中增加新的数据源参数
2、数据源配置类中,新增配置源bean,并且把数据源set到targetDataSource中
3、DataSourceType枚举类中,添加新数据源的枚举
如图所示:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这篇关于springboot实现多数据源配置(Druid/Hikari)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/338613

相关文章

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

Java中ArrayList和LinkedList有什么区别举例详解

《Java中ArrayList和LinkedList有什么区别举例详解》:本文主要介绍Java中ArrayList和LinkedList区别的相关资料,包括数据结构特性、核心操作性能、内存与GC影... 目录一、底层数据结构二、核心操作性能对比三、内存与 GC 影响四、扩容机制五、线程安全与并发方案六、工程

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.