SpringBoot + Mybatis Plus + Druid 实现多数据源切换和动态事务

SpringBoot + Mybatis Plus + Druid 实现多数据源切换和动态事务,第1张

概述之前配置多数据源踩了很多坑,包括事务注解失效,多数据源无法切换等,特此把配置过程整理下来,以供后来的小伙伴参考。

目录 一. 相关博客二. 基本环境三. 配置过程@L_502_3@2. 修改 yml 配置文件3. 数据源枚举 DBTypeEnum4. 标记数据源的注解 MyDataSource5. 动态数据源管理器 DataSourceContextHolder6. 动态数据源决策 DynamicDataSource7. Mybatis Plus 的配置类 MybatisPlusConfig8. 使用AOP实现数据源的动态设置9.实际使用:在ServiceImpl 类中使用注解进行标识10.测试结果


一. 相关博客 之前的几篇博客中分析了 Mybatis Plus 的基本用法:


MyBatis Plus 的使用之入门

MyBatis Plus 的自动填充功能


二. 基本环境

本篇博客针对相对复杂的多数据源和动态事务的配置展开介绍,基本环境如下:

SpringBoot : 2.2.4.RELEASE

DruID : 1.1.21

Mybatis Plus : 3.4.0


三. 配置过程 1. 引入依赖
	<!-- MyBatis Plus 依赖 -->        <dependency>            <groupID>com.baomIDou</groupID>            <artifactID>mybatis-plus-boot-starter</artifactID>            <version>3.4.0</version>        </dependency>        	<!-- druID -->    <dependency>        <groupID>com.alibaba</groupID>        <artifactID>druID-spring-boot-starter</artifactID>        <version>1.1.21</version>        <scope>compile</scope>   </dependency>
2. 修改 yml 配置文件
spring:  datasource:    druID:      db1:        # MysqL默认数据库的配置        driverClassname: com.MysqL.cj.jdbc.Driver        url: jdbc:MysqL://127.0.0.1:3306/backend_dev?useUnicode=true&characterEnCoding=UTF-8&serverTimezone=GMT%2B8        username: root        password: 123456        initialSize: 5        minIDle: 5        maxActive: 20        # 配置获取连接等待超时的时间        maxWait: 60000        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒        timeBetweenevictionRunsMillis: 60000        # 配置一个连接在池中最小生存的时间,单位是毫秒        minevictableIDleTimeMillis: 300000        valIDationquery: SELECT 1 FROM DUAL        testWhileIDle: true        testOnBorrow: false        testOnReturn: false      db2:        # MysqL第二个库的配置        driverClassname: com.MysqL.cj.jdbc.Driver        url: jdbc:MysqL://127.0.0.1:3306/backend_log_dev?useUnicode=true&characterEnCoding=UTF-8&serverTimezone=GMT%2B8        username: root        password: 123456        initialSize: 5        minIDle: 5        maxActive: 20        # 配置获取连接等待超时的时间        maxWait: 60000        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒        timeBetweenevictionRunsMillis: 60000        # 配置一个连接在池中最小生存的时间,单位是毫秒        minevictableIDleTimeMillis: 300000        valIDationquery: SELECT 1 FROM DUAL        testWhileIDle: true        testOnBorrow: false        testOnReturn: false
3. 数据源枚举 DBTypeEnum
/** * @author jichunyang * @description 数据源枚举 */public enum DBTypeEnum {     DB1("db1"), DB2("db2");    private String value;     DBTypeEnum(String value) {        this.value = value;    }     public String getValue() {        return value;    }}
4. 标记数据源的注解 MyDataSource
/** * @author jichunyang * @description 数据源注解 */@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@documentedpublic @interface MyDataSource {    DBTypeEnum value() default DBTypeEnum.DB1;}
5. 动态数据源管理器 DataSourceContextHolder
/** * @author: jichunyang * @description: 设置、获取数据源 * @date: 2020/9/14 17:24 **/public class DataSourceContextHolder {    private static final ThreadLocal contextHolder = new ThreadLocal<>(); //开启多个线程,每个线程初始化一个数据源    /**     * 设置数据源     * @param dbTypeEnum     */    public static voID setDbType(DBTypeEnum dbTypeEnum) {        contextHolder.set(dbTypeEnum.getValue());    }    /**     * 取得当前数据源     * @return     */    public static String getDbType() {        return (String) contextHolder.get();    }    /**     * 清除上下文数据     */    public static voID clearDbType() {        contextHolder.remove();    }}
6. 动态数据源决策 DynamicdataSource
/** * @author: jichunyang * @description: 动态数据源实现 * @date: 2020/9/14 17:22 **/@Slf4jpublic class DynamicdataSource extends AbstractRoutingDataSource {    @OverrIDe    protected Object determineCurrentLookupKey() {        String datasource = DataSourceContextHolder.getDbType();        log.deBUG("当前使用数据源:{}", datasource);        return datasource;    }}
7. Mybatis Plus 的配置类 MybatisPlusConfig

mapper 的目录结构修改如下:


如果使用了 xml 文件进行多表查询,则还需要修改对应的 xml 文件中的路径 namespace:

<?xml version="1.0" enCoding="UTF-8" ?><!DOCTYPE mapper        PUBliC "-//mybatis.org//DTD Mapper 3.0//EN"        "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!-- 此处与接口类地址对应 --><mapper namespace="com.system.domain.mapper.db1.DataMapper">    <select ID="pageDatas" resultType="map">        ...        <!-- 对应的pageDatas查询sql -->    </select></mapper>

mapper 所在目录为 com.system.domain.mapper,因此 @MapperScan 需要扫描的路径为mapper下的 db1 和 db2,配置如下:

com.system.domain.mapper.db*

MybatisPlusConfig 配置类

/** * @author jichunyang * @description mybatis plus配置 */@EnableTransactionManagement //开启事务@Configuration@MapperScan("com.system.domain.mapper.db*")public class MybatisPlusConfig {    /**     * 分页插件     */    @Bean    public PaginationInterceptor paginationInterceptor() {        return new PaginationInterceptor();    }    @Bean(name = "db1")    @ConfigurationPropertIEs(prefix = "spring.datasource.druID.db1")    public DataSource db1() {        return DruIDDataSourceBuilder.create().build();    }    @Bean(name = "db2")    @ConfigurationPropertIEs(prefix = "spring.datasource.druID.db2")    public DataSource db2() {        return DruIDDataSourceBuilder.create().build();    }    /**     * 动态数据源配置     *     * @return     */    @Bean(name = "multipleDataSource")    @Primary    public DataSource multipleDataSource(@QualifIEr("db1") DataSource db1,                                         @QualifIEr("db2") DataSource db2) {        DynamicdataSource dynamicdataSource = new DynamicdataSource();        Map<Object, Object> targetDataSources = new HashMap<>();        targetDataSources.put(DBTypeEnum.DB1.getValue(), db1);        targetDataSources.put(DBTypeEnum.DB2.getValue(), db2);        dynamicdataSource.setTargetDataSources(targetDataSources);        // 程序默认数据源,根据程序调用数据源频次,把常调用的数据源作为默认        dynamicdataSource.setDefaultTargetDataSource(db1);        return dynamicdataSource;    }    @Bean("sqlSessionFactory")    public sqlSessionFactory sqlSessionFactory() throws Exception {        MybatissqlSessionfactorybean sqlSessionFactory = new MybatissqlSessionfactorybean();        sqlSessionFactory.setDataSource(multipleDataSource(db1(), db2()));        // 设置默认需要扫描的 xml 文件        sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml"));		//其他配置项        MybatisConfiguration configuration = new MybatisConfiguration();        configuration.setJdbcTypeForNull(JdbcType.NulL);        // 驼峰和下划线转换        configuration.setMapUnderscoreToCamelCase(true);        configuration.setCacheEnabled(false);        configuration.setCallSettersOnNulls(true);        sqlSessionFactory.setConfiguration(configuration);        sqlSessionFactory.setTypeAliasesPackage("com.intyt.jcyy.system.domain");        // 数据库查询结果驼峰式返回        sqlSessionFactory.setobjectWrapperFactory(new MybatisMapWrapperFactory());        // 添加分页功能        sqlSessionFactory.setPlugins(new Interceptor[]{                paginationInterceptor()        });        // 实现自动填充功能        sqlSessionFactory.setGlobalConfig(globalConfiguration());        return sqlSessionFactory.getobject();    }    @Bean(name = "multipleTransactionManager")    @Primary    public DataSourceTransactionManager multipleTransactionManager(@QualifIEr("multipleDataSource") DataSource dataSource) {    	// 动态事务配置        return new DataSourceTransactionManager(dataSource);    }    @Bean    public GlobalConfig globalConfiguration() {    	// 自动填充创建时间和更新时间(MyMetaObjectHandler的实现参考 “MyBatis Plus 的自动填充功能” 博客)        GlobalConfig conf = new GlobalConfig();        conf.setMetaObjectHandler(new MyMetaObjectHandler());        return conf;    }
8. 使用AOP实现数据源的动态设置
/** * @author: jichunyang * @description: aop实现数据源切换 * @date: 2020/9/14 17:29 **/@Component@Order(value = -100)@Slf4j@Aspectpublic class DataSourceAspect {    @Before("execution(* com.system.service.impl.*.*(..)) && @annotation(com.common.annotation.MyDataSource)")    public voID before(JoinPoint joinPoint) {    	// execution 中配置的是服务实现类 & MyDataSource的包路径        MethodSignature signature = (MethodSignature) joinPoint.getSignature();        Method method = signature.getmethod();        MyDataSource myDataSource = null;        // 判断方法上的注解        if (method.isAnnotationPresent(MyDataSource.class)) {            myDataSource = method.getAnnotation(MyDataSource.class);            DataSourceContextHolder.setDbType(myDataSource.value());        } else if (method.getDeclaringClass().isAnnotationPresent(MyDataSource.class)) {            //其次判断类上的注解            myDataSource = method.getDeclaringClass().getAnnotation(MyDataSource.class);            DataSourceContextHolder.setDbType(myDataSource.value());        }        if (myDataSource != null) {            log.info("注解方式选择数据源---" + myDataSource.value().getValue());        }    }    /**     * 服务类的方法结束后,会清除数据源,此时会变更为默认的数据源     **/    @After("execution(* com.system.service.impl.*.*(..)) && @annotation(com.common.annotation.MyDataSource)")    public voID after(JoinPoint point){    	// execution 中配置的是服务实现类 & MyDataSource的包路径        DataSourceContextHolder.clearDbType();    }}
9.实际使用:在ServiceImpl 类中使用注解进行标识

即所有被标识了以下事务注解 @Transactional 和数据源注解 @MyDataSource 的service实现类都可以实现动态数据源和事务的切换:

@MyDataSource(DBTypeEnum.DB1)@Transactional
10.测试结果


如果在按照以上步骤配置后,发现数据源切换不生效,可检查是否是 @Transactional 注解失效,可参考以下博客:

Spring的声明式事务@Transactional注解的6种失效场景

欢迎关注我的公众号,用讲故事的方式学技术。

这里有脑洞大开的奇葩故事,也有温暖文艺的心灵感悟。

技术知识,也可以很有趣。

总结

以上是内存溢出为你收集整理的SpringBoot + Mybatis Plus + Druid 实现多数据源切换和动态事务全部内容,希望文章能够帮你解决SpringBoot + Mybatis Plus + Druid 实现多数据源切换和动态事务所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/1210644.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-04
下一篇 2022-06-04

发表评论

登录后才能评论

评论列表(0条)

保存