基于SpringBoot+Druid实现多数据源:原生注解式

前言

本博客姊妹篇

一、功能描述

  • 配置方式:配置文件中实现多数据源,非动态
  • 使用方式:使用注解切换数据源

二、代码实现

2.1 配置

yaml 复制代码
# spring配置
spring:
  # 数据源配置
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      web-stat-filter:
        enabled: true
        url-pattern: /*
        exclusions: '*.js,*.css,*.gif,*.png,*.jpg,*.ico,/druid/*'
      stat-view-servlet:
        enabled: true
        url-pattern: /druid/*
        login-username: admin
        login-password: 123456
      filter:
        stat:
          enabled: true
          log-slow-sql: true
          slow-sql-millis: 1000
          merge-sql: true
        wall:
          enabled: true
          config:
            multi-statement-allow: true
      master:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/boot_business?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8&useSSL=false
        username: root
        password: root
        initial-size: 10
        min-idle: 10
        max-active: 100
        max-wait: 60000
        time-between-eviction-runs-millis: 60000
        min-evictable-idle-time-millis: 300000
        validation-query: select 1
        test-while-idle: true
        test-on-borrow: false
        test-on-return: false
        pool-prepared-statements: true
        max-pool-prepared-statement-per-connection-size: 20
      slave:
        enabled: true
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/boot_codegen?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8&useSSL=false
        username: root
        password: root
        initial-size: 10
        min-idle: 10
        max-active: 100
        max-wait: 60000
        time-between-eviction-runs-millis: 60000
        min-evictable-idle-time-millis: 300000
        validation-query: select 1
        test-while-idle: true
        test-on-borrow: false
        test-on-return: false
        pool-prepared-statements: true
        max-pool-prepared-statement-per-connection-size: 20

2.2 配置类

java 复制代码
package com.qiangesoft.datasource.core;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import lombok.extern.slf4j.Slf4j;
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 java.util.HashMap;
import java.util.Map;

/**
 * 多数据源配置
 *
 * @author qiangesoft
 * @date 2024-03-14
 */
@Slf4j
@Configuration
public class DataSourceConfiguration {

    @Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DruidDataSource masterDataSource() {
        DruidDataSource masterDataSource = DruidDataSourceBuilder.create().build();
        masterDataSource.setName(DataSourceType.MASTER.getType());
        return masterDataSource;
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
    public DruidDataSource slaveDataSource() {
        DruidDataSource slaveDataSource = DruidDataSourceBuilder.create().build();
        slaveDataSource.setName(DataSourceType.SLAVE.getType());
        return slaveDataSource;
    }

    @Bean
    @Primary
    public DynamicDataSource dynamicDataSource(DruidDataSource masterDataSource, DruidDataSource slaveDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(masterDataSource.getName(), masterDataSource);
        targetDataSources.put(slaveDataSource.getName(), slaveDataSource);
        return new DynamicDataSource(masterDataSource, targetDataSources);
    }
}

2.3 多数据源扩展实现

java 复制代码
package com.qiangesoft.datasource.core;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;
import java.util.Map;

/**
 * 动态数据源
 *
 * @author qiangesoft
 * @date 2024-03-14
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContext.getDataSource();
    }
}

2.4 切面

java 复制代码
package com.qiangesoft.datasource.core;

import lombok.extern.slf4j.Slf4j;
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.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Objects;

/**
 * 多数据源处理
 *
 * @author qiangesoft
 * @date 2024-03-14
 */
@Slf4j
@Order(1)
@Aspect
@Component
public class DataSourceAspect {

    /**
     * 切点
     */
    @Pointcut("@annotation(com.qiangesoft.datasource.core.DataSource)")
    public void pointCut() {
    }

    /**
     * 通知
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        DataSource dataSource = this.getDataSource(joinPoint);
        if (dataSource == null) {
            DataSourceContext.setDataSource(DataSourceType.MASTER);
        } else {
            DataSourceContext.setDataSource(dataSource.value());
        }
        try {
            return joinPoint.proceed();
        } finally {
            DataSourceContext.removeDataSource();
        }
    }

    /**
     * 获取数据源
     *
     * @param joinPoint
     * @return
     */
    public DataSource getDataSource(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 方法上查找注解
        DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
        if (Objects.nonNull(dataSource)) {
            return dataSource;
        }
        // 类上查找注解
        return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
    }
}

2.5 线程本地变量

java 复制代码
package com.qiangesoft.datasource.core;

/**
 * 数据源上下文
 *
 * @author qiangesoft
 * @date 2024-03-14
 */
public class DataSourceContext {

    /**
     * 线程本地变量:数据源
     */
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    /**
     * 设置数据源的变量
     */
    public static void setDataSource(DataSourceType dataSourceType) {
        CONTEXT_HOLDER.set(dataSourceType.getType());
    }

    /**
     * 获得数据源的变量
     */
    public static String getDataSource() {
        return CONTEXT_HOLDER.get();
    }

    /**
     * 清空数据源变量
     */
    public static void removeDataSource() {
        CONTEXT_HOLDER.remove();
    }
}

2.6 使用

java 复制代码
package com.qiangesoft.datasource.controller;

import com.qiangesoft.datasource.core.DataSource;
import com.qiangesoft.datasource.core.DataSourceType;
import com.qiangesoft.datasource.entity.SysUser;
import com.qiangesoft.datasource.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * <p>
 * 用户信息 前端控制器
 * </p>
 *
 * @author qiangesoft
 * @since 2024-03-14
 */
@RestController
@RequestMapping("/sys/user")
public class SysUserController {

    @Autowired
    private ISysUserService sysUserService;

    @DataSource(DataSourceType.MASTER)
    @GetMapping("/master")
    public List<SysUser> listMaster() {
        return sysUserService.list();
    }

    @DataSource(DataSourceType.SLAVE)
    @GetMapping("/slave")
    public List<SysUser> listSlave() {
        return sysUserService.list();
	}

}
相关推荐
yuuki2332334 分钟前
【C语言】文件操作(附源码与图片)
c语言·后端
IT_陈寒7 分钟前
Python+AI实战:用LangChain构建智能问答系统的5个核心技巧
前端·人工智能·后端
无名之辈J31 分钟前
系统崩溃(OOM)
后端
来旺33 分钟前
互联网大厂Java面试全解析及三轮问答专项
java·数据库·spring boot·安全·缓存·微服务·面试
码农刚子40 分钟前
ASP.NET Core Blazor简介和快速入门 二(组件基础)
javascript·后端
间彧40 分钟前
Java ConcurrentHashMap如何合理指定初始容量
后端
摇滚侠41 分钟前
Spring Boot 3零基础教程,yml文件中配置和类的属性绑定,笔记15
spring boot·redis·笔记
thginWalker1 小时前
使用Spring Boot构建消息通信层
spring boot
lang201509281 小时前
Spring Boot 外部化配置最佳实践指南
java·spring boot
catchadmin1 小时前
PHP8.5 的新 URI 扩展
开发语言·后端·php