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


一. 相关博客

之前的几篇博客中分析了 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)
@Documented
public @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
 **/

@Slf4j
public 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
@Aspect
public 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种失效场景

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

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

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

在这里插入图片描述

原文地址:https://blog.csdn.net/j1231230

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


1.pom.xml引入依赖 &lt;dependency&gt; &lt;groupId&gt;com.github.pagehelper&lt;/groupId&gt; &lt;artifactId&gt;pagehelper&lt;/artifactId&gt; &lt;version&gt;5
&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&gt; &lt;!DOCTYPE configuration PUBLIC &quot;-//mybatis.org//DTD Config 3.0//EN&quot; &qu
准备工作 ① 创建数据库&amp;数据表 ## 创建数据库 CREATE DATABASE `dbtest1`; ## 创建数据表 CREATE TABLE `t_user` ( `id` INT NOT NULL AUTO_INCREMENT, `username` VARCHAR(20) DEF
MyBatis逆向工程是指根据数据库表结构自动生成对应的实体类、Mapper接口以及SQL映射文件的过程。这个过程可以通过MyBatis提供的逆向工程工具来完成,极大地方便了开发人员,避免了重复的代码编写,提高了开发效率。 创建逆向工程的步骤 1、添加依赖&amp;插件 &lt;!-- 控制Mave
MyBatis获取参数值的两种方式:${}和#{} ${}的本质就是字符串拼接,#{}的本质就是占位符赋值。 ${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号;但是#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时,可以自
resultMap作用是处理数据表中字段与java实体类中属性的映射关系。 准备工作 ① 创建数据库&amp;数据表 CREATE DATABASE `dbtest1`; CREATE TABLE `t_emp` ( `emp_id` int NOT NULL AUTO_INCREMENT, `em
EHCache缓存针对于MyBatis的二级缓存。 MyBatis默认二级缓存是SqlSessionFactory级别的。 添加依赖 &lt;!-- MyBatis-EHCache整合包 --&gt; &lt;dependency&gt; &lt;groupId&gt;org.mybatis.cac
MyBatis 提供了一级缓存和二级缓存的支持,用于提高数据库查询的性能,减少不必要的数据库访问。 一级缓存(SqlSession 级别的缓存) 一级缓存是 MyBatis 中最细粒度的缓存,也称为本地缓存。它存在于每个 SqlSession 的生命周期中,当 SqlSession 被关闭或清空时,
动态SQL是 MyBatis 中非常强大且灵活的功能,允许你根据不同的条件构建SQL查询。 这主要通过 &lt;if&gt;、&lt;choose&gt;、&lt;when&gt;、&lt;otherwise&gt;、&lt;foreach&gt;等标签实现。 查询场景 /** * 根据条件查询员工
本教程操作系统:windows10系统、DELL G3电脑。 MyBatis 是一个优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。在 MyBatis 中,配置数据库连接是非常重要的第一步。下面将详细介绍如何配置 MyBatis 的
今天小编给大家分享的是MyBatis批量查询、插入、更新、删除如何实现,相信很多人都不太了解,为了让大家更加了解,所以给大家总结了以下内容,一起往下看吧。
今天小编给大家分享的是Mybatis操作多数据源实现的方法,相信很多人都不太了解,为了让大家更加了解,所以给大家总结了以下内容,一起往下看吧。一定会有所收获...
本篇文章和大家了解一下mybatis集成到spring的方式有哪些。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。1 前言1.1 集成spring前使用mybat...
今天小编给大家分享的是mybatis-plus分页查询的3种方法,相信很多人都不太了解,为了让大家更加了解,所以给大家总结了以下内容,一起往下看吧。一定会有所收获...
本篇内容主要讲解“mybatis之BaseTypeHandler怎么使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“mybatis...
这篇文章主要介绍了mybatisforeach怎么传两个参数的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇mybatisforeach怎...
这篇“MyBatis映射文件中parameterType与resultType怎么使用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的...
这篇文章主要介绍“MyBatis怎么获取自动生成的键值”,在日常操作中,相信很多人在MyBatis怎么获取自动生成的键值问题上存在疑惑,小编查阅了各式资料,整理出
这篇文章主要讲解了“怎么去掉IntelliJIDEA中mybatis对应的xml文件警告”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入...
这篇文章主要介绍“MybatisPlus使用@TableId主键id自增长无效如何解决”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这...