IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> SpringBoot多数据源配置以及动态切换 -> 正文阅读

[Java知识库]SpringBoot多数据源配置以及动态切换

SpringBoot多数据源配置以及动态切换

原理:利用切面编程,自定义注解实现多数据源的配置以及动态切换

1.核心依赖和yml文件的配置

核心依赖

 <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.1</version>
        </dependency>
         <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

ym配置文件

server:
  port: 8080
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    druid:
      test:
        url: jdbc:mysql://localhost:3306/test
        username: root
        password: 123456
        connection-properties: clientEncoding=UTF-8;allowMultiQueries=true
      user:
        url: jdbc:mysql://localhost:3306/user
        username: root
        password: 123456
      # 初始连接数
      initialSize: 5
      # 最小连接池数量
      minIdle: 10
      # 最大连接池数量
      maxActive: 20
      # 配置获取连接等待超时的时间
      maxWait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      timeBetweenEvictionRunsMillis: 60000
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      minEvictableIdleTimeMillis: 300000
      # 配置一个连接在池中最大生存的时间,单位是毫秒
      maxEvictableIdleTimeMillis: 900000
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      # 打开PSCache,并且指定每个连接上PSCache的大小
      poolPreparedStatements: true
      maxPoolPreparedStatementPerConnectionSize: 20
      # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙,此处是filter修改的地方
      filters:
        commons-log.connection-logger-name: stat,wall,log4j
    # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
      # 合并多个DruidDataSource的监控数据
      useGlobalDataSourceStat: true
      # 配置 DruidStatFilter
      web-stat-filter:
        enabled: true
        url-pattern:
mybatis:
  #实体类所做包
  #type-aliases-package:
  #mapper.xml所在位置
  mapper-locations: classpath:mappers/**.xml
logging:
  level:
    root: info

3.定义枚举类(或者常量类或者接口)设置数据源标识

/**
 * @Author yfx
 * @Date 2021/7/22
 * @Description 数据源类型
 */
public enum DataSourceType {
    TEST,
    USER
}

4.自定义注解,用于切换数据源

/**
 * @Author yfx
 * @Date 2021/7/22
 * @Description 自定义注解,用于方法或者类名
 */
@Target({ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource {
    DataSourceType value() default DataSourceType.TEST;
}

5 维护一个ThreadLocal来存取数据源标识

/**
 * @Author yfx
 * @Date 2021/7/22
 * @Description  数据源切换处理
 */
@Slf4j
public class DynamicDataSourceContextHolder {

    /**
     *此类提供线程局部变量。这些变量不同于它们的正常对应关系是每个线程访问一个线程(通过get、set方法),有自己的独立初始化变量的副本。
     */
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    /**
     * 设置当前线程的数据源变量
     */
    public static void setDataSourceType(String dataSourceType) {
        log.info("已切换到{}数据源", dataSourceType);
        contextHolder.set(dataSourceType);
    }

    /**
     * 获取当前线程的数据源变量
     */
    public static String getDataSourceType() {
        return contextHolder.get();
    }

    /**
     * 删除与当前线程绑定的数据源变量
     */
    public static void removeDataSourceType() {
        contextHolder.remove();
    }
    
}

ps: ThreadLocal类似存放线程的局部变量,实际是维护了一个ThreadLocalMap,ThreadLocalMap通过ThreadLocal的静态内部类实现,其key为ThreadLocal对象,是弱引用;其value一般为强引用对象,每次调用set,get,remove方法都会清除ThreadLocalMap中key为null的键值对。
详请可参考该博客:

6 获取数据源

定义一个类继承AbstractRoutingDataSource实现determineCurrentLookupKey方法,该方法可以实现数据库的动态切换

/**
 * @Author yfx
 * @Date 2021/7/22
 * @Description 获取数据源(依赖于 spring)
 * 定义一个类继承AbstractRoutingDataSource实现determineCurrentLookupKey方法,
 * 该方法可以实现数据库的动态切换
 */
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {

    public static  DynamicDataSource build() {
        return new DynamicDataSource();
    }

    /**
     * 获取与数据源相关的key
     * 此key是Map<String,DataSource> resolvedDataSources 中与数据源绑定的key值
     * 在通过determineTargetDataSource获取目标数据源时使用
     */
    @Override
    protected Object determineCurrentLookupKey() {
        log.info("获取数据源:"+DynamicDataSourceContextHolder.getDataSourceType());
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

7.配置数据源

/**
 * @Author yfx
 * @Date 2021/7/22
 * @Description 数据源配置类
 */
@Configuration
public class DataSourceConfig {


    /**
     * test数据源
     */
    @Bean
    @ConfigurationProperties("spring.datasource.druid.test")
    public javax.sql.DataSource testDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * user数据源
     */
    /* @ConditionalOnProperty(prefix = "spring.datasource.druid.user", name = "enabled", havingValue = "true")
     // 是否开启数据源开关---若不开启 默认适用默认数据源*/
    @Bean
    @ConfigurationProperties("spring.datasource.druid.user")
    public javax.sql.DataSource userDataSource() {
        return DruidDataSourceBuilder.create().build();
    }
    /**
     * 设置数据源
     */
    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dynamicDataSource(javax.sql.DataSource testDataSource, javax.sql.DataSource userDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        DynamicDataSource dynamicDataSource = DynamicDataSource.build();
        targetDataSources.put(DataSourceType.TEST.name(), testDataSource);
        targetDataSources.put(DataSourceType.USER.name(), userDataSource);
        //默认数据源配置 DefaultTargetDataSource
        dynamicDataSource.setDefaultTargetDataSource(testDataSource);
        //额外数据源配置 TargetDataSources
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.afterPropertiesSet();
        return dynamicDataSource;
    }
}

8 多数据源切面配置

/**
 * @Author yfx
 * @Date 2021/7/22
 * @Description 多数据源切面配置类
 */
@Aspect
@Component
// 保证该AOP在@Transactional之前执行
@Order(1)
@Slf4j
public class DynamicDataSourceAspect {



    @Pointcut("@annotation(com.yfx.task.config.datasource.DataSource)"
            + "|| @within(com.yfx.task.config.datasource.DataSource)")
    public void pointCut()  {
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        Method targetMethod = this.getTargetMethod(point);
        // 获取要切换的数据源
        DataSource dataSource = targetMethod.getAnnotation(DataSource.class);
        if (dataSource != null)  {
            System.out.println("代理:"+dataSource.value().name());
            DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
        }
        try {
            return point.proceed();
        }
        finally  {
            // 销毁数据源 在执行方法之后
            DynamicDataSourceContextHolder.removeDataSourceType();
        }
    }

    /**
     * 获取目标方法
     * @param pjp
     * @return
     * @throws NoSuchMethodException
     */
    private Method getTargetMethod(ProceedingJoinPoint pjp) throws NoSuchMethodException {
        Signature signature = pjp.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        return methodSignature.getMethod();
    }
}

9 结果调试

需要注释掉以下注解

在这里插入图片描述
否则可能出现以下错误,不能正常切换数据源:
在这里插入图片描述

PS:本文还用于练习Mybatis一对一和一对多练习

DTO类结构

/**
 * @Author yfx
 * @Date 2021/7/22
 * @Description
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO {
    private Integer uid;

    private String username;

    private String password;

    private String nickname;

    private String gender;

    private String photo;

    private List<Role> roleList;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class StuDTO {
    private Integer sid;
    private String  sno;
    private String  sname;
    private Integer  age;
    private Integer sex;
    private Integer classes;
    private Score score;
}

xml配置

<resultMap id="userDTO" type="com.yfx.task.pojo.dto.UserDTO">
        <id property="uid" column="uid"/>
        <result property="username" column="username"/>
        <result property="password" column="password"/>
        <result property="nickname" column="nickname"/>
        <result property="gender" column="gender"/>
        <result property="photo" column="photo"/>
        <collection property="roleList" ofType="com.yfx.task.pojo.entity.Role">
            <id property="id" column="rid"/>
            <result property="name" column="name"/>
        </collection>
    </resultMap>

    <resultMap id="StuDTO" type="com.yfx.task.pojo.dto.StuDTO">
        <id property="sid" column="sid"/>
        <result property="sno" column="sno"/>
        <result property="sname" column="sname"/>
        <result property="age" column="age"/>
        <result property="sex" column="sex"/>
        <result property="classes" column="classes"/>
        <association property="score" javaType="com.yfx.task.pojo.entity.Score">
            <id property="scid" column="scid"/>
            <result property="sid" column="sid"/>
            <result property="cid" column="cid"/>
            <result property="score" column="score"/>
        </association>
    </resultMap>

    <sql id="userDTO_columns">
        user.uid, user.username, user.password, user.nickname, user.gender, user.photo, role.id rid, role.name
    </sql>

    <sql id="StuDTO_columns">
     stu.*,score.score
    </sql>

    <select id="oneToMany" resultMap="userDTO">
       select <include refid="userDTO_columns"/> from role
        left join user_role  on role.id = user_role.rid
        left join user on user.uid = user_role.uid
        limit 0 ,10
    </select>

    <select id="oneToOne" resultMap="StuDTO">
        select <include refid="StuDTO_columns"/> from stu
        left join score  on stu.sid = score.sid
    </select>
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-07-24 00:03:28  更:2021-07-24 00:04:16 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/7 4:26:54-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码