文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

【万字长文】SpringBoot整合MyBatis搭建MySQL多数据源完整教程(提供Gitee源码)

2023-09-02 18:40

关注

前言:在我往期的博客介绍了2种关于如何使用SpringBoot搭建多数据源操作,本期博客我参考的是目前主流的框架,把最后一种整合多数据源的方式以博客的形式讲解完,整合的过程比较传统和复杂,不过我依旧会把每个实体类的思路都给大家讲解清楚的,项目的最后我都会提供Gitee源码地址。

往期博客:

第一种:SpringBoot+Jpa配置Oracle多数据源(提供Gitee源码)

第二种:SpringBoot+Mybatis搭建Oracle多数据源配置简述(提供Gitee源码)

后续补充:

【万字长文】SpringBoot整合Atomikos实现多数据源分布式事务(提供Gitee源码)

目录

一、导入pom依赖

二、yml配置文件

三、数据源枚举类 

四、Spring工具类

五、配置类

5.1、DynamicDataSourceContextHolder数据源切换处理类

5.2、DynamicDataSource动态数据源路由类

5.3、DruidProperties配置属性

5.4、DruidConfig多数据源核心配置类 

六、DataSource自定义多数据源切换注解

七、DataSourceAspect动态数据源的切面类 

八、项目完整截图

九、使用方法

十、Gitee源码 

十一、总结


                            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                    
# Mybatis配置mybatis:  # 配置mapper的扫描,找到所有的mapper.xml映射文件  mapper-locations: classpath:mapper    MASTER,        SLAVE}

主要作用是提供通过名字获取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

2DynamicDataSource
自定义动态数据源,内部持有多个目标数据源Map,可通过设置key动态切换数据源。

3DruidProperties
用于读取Druid数据源的配置属性,比如最大连接数、最小连接数等。

4DruidConfig
实现了Druid的多数据源配置,创建了masterslave两个数据源bean,然后通过DynamicDataSource组装成一个动态数据源。

5、DataSource
注解用于标注方法或类上,指定使用哪个数据源。

DataSourceAspect
AOP切面,在方法执行前通过DataSource注解获取数据源key,设置到DynamicDataSourceContextHolder中,以切换到指定数据源。 

工作流程是

1DruidConfig先创建多个数据源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 targetDataSources){    super.setDefaultTargetDataSource(defaultTargetDataSource);    super.setTargetDataSources(targetDataSources);    super.afterPropertiesSet();}

实现了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 targetDataSources)    {        super.setDefaultTargetDataSource(defaultTargetDataSource);        super.setTargetDataSources(targetDataSources);        super.afterPropertiesSet();    }    @Override    protected Object determineCurrentLookupKey()    {        return DynamicDataSourceContextHolder.getDataSourceType();    }}

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 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获取对应的数据源实例。 

实现动态数据源的配置

第一步、创建一个Map用于存放目标数据源,并将名为masterDataSource的Bean作为主数据源放入Map。

第二步、调用setDataSource方法将名为slaveDataSource的Bean放入Map,Key设置为SLAVE。

最后、使用主数据源实例和数据源Map创建DynamicDataSource实例,并设置为Primary,即默认的数据源。

@Bean(name = "dynamicDataSource")@Primarypublic DynamicDataSource dataSource(DataSource masterDataSource){    Map 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;@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 targetDataSources = new HashMap<>();        targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);        setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");        return new DynamicDataSource(masterDataSource, targetDataSources);    }        public void setDataSource(Map targetDataSources, String sourceName, String beanName)    {        try        {            DataSource dataSource = SpringUtils.getBean(beanName);            targetDataSources.put(sourceName, dataSource);        }        catch (Exception e)        {            e.printStackTrace();        }    }}

@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;}

@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();}

运行结果如下: 

在yml文件配置好自己的主从数据源一键启动项目即可

项目地址:SpringBoot整合MyBatis搭建MySQL多数据源

以上就是我对于SpringBoot如何整合多数据源的技术分析,也是比较传统化的方式,比较复杂,如有问题欢迎评论区讨论!

来源地址:https://blog.csdn.net/HJW_233/article/details/132032716

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-数据库
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯