SpringBoot - 多数据源

108 阅读3分钟

1、引入数据库相关依赖

<!--数据库相关-->
<dependency>
  <groupId>com.mysql</groupId>
  <artifactId>mysql-connector-j</artifactId>
  <version>8.2.0</version>
</dependency>
<dependency>
  <groupId>org.mybatis.spring.boot</groupId>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <version>2.1.4</version>
</dependency>
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid-spring-boot-starter</artifactId>
  <version>1.2.8</version>
</dependency>

2、配置多数据源

例如下面yml 中我配置了 ali 和 local 两个数据源,名称自己随便取

spring:
  datasource:
    ali:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://139.9.168.24:3306/ctools?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
      username: root
      password: 123456
      type: com.alibaba.druid.pool.DruidDataSource # 指定使用Druid数据源
      # Druid连接池配置
      initial-size: 5
      min-idle: 5
      max-active: 20
      max-wait: 60000
      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
      filters: stat,wall
      connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
    local:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/cl_test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
      username: root
      password: 123456
      type: com.alibaba.druid.pool.DruidDataSource # 指定使用Druid数据源
      # Druid连接池配置
      initial-size: 5
      min-idle: 5
      max-active: 20
      max-wait: 60000
      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
      filters: stat,wall
      connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

      # useUnicode=true:表示使用Unicode字符集。
      # characterEncoding=UTF-8:表示使用UTF-8字符编码。
      # autoReconnect=true:表示在连接断开时自动重新连接。
      # useSSL=false:表示不使用SSL加密连接。

3、数据库配置

  • 多数据源配置

配置自定义 DataSourceRouting 数据库路由的Bean

@Primary 数据表示使用那个作为默认数据源

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;

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

@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.ali")
    public DataSource aliDB() {
        return DataSourceBuilder.create().type(DruidDataSource.class).build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.local")
    public DataSource localDB() {
        return DataSourceBuilder.create().type(DruidDataSource.class).build();
    }

    @Bean
    @Primary
    @DependsOn(value = {"aliDB", "localDB"})
    public DataSource getDB() {
        DataSourceRouting routing = new DataSourceRouting();
        Map<Object, Object> dbMap = new HashMap<>();
        dbMap.put("aliDB", aliDB());
        dbMap.put("localDB", localDB());
        routing.setTargetDataSources(dbMap);
        routing.setDefaultTargetDataSource(localDB());
        return routing;
    }

}
  • 路由配置

要实现 AbstractRoutingDataSource 抽象类

需要用到自定义的DataSourceHelper 类的 ThreadLocal 来切换数据源

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

public class DataSourceRouting extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceHelper.get();
    }
}
  • 自定义 DataSourceHelper 类,ThreadLocal 用来指定当前线程的数据源
public class DataSourceHelper {
    private static final ThreadLocal<String> dbname = new ThreadLocal<String>();

    public static String get() {
        return dbname.get();
    }

    public static void set(String name) {
        dbname.set(name);
    }

    public static void remove() {
        dbname.remove();
    }
}

4、测试

import com.springboot.config.DataSourceHelper;
import com.springboot.mapper.UserMapper;
import com.springboot.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
class DemoApplicationTests {
    @Resource
    private UserMapper userMapper; // ali
    @Resource
    private UserInfoMapper userInfoMapper; // 本地

    @Test
    void contextLoads() {
        // 本地
        List<UserInfo> list = userInfoMapper.list();
        list.forEach(System.out::println);

        // 切换到 ali 数据源
        DataSourceHelper.set("aliDB");
        List<User> list02 = userMapper.list();
        list02.forEach(System.out::println);
    }

}

补充:使用注解方式来切换数据源

1、添加 AOP 依赖

<!--aop 相关依赖-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2、启动类上配置启用AOP

@EnableAspectJAutoProxy 开启AOP

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@SpringBootApplication
@MapperScan(value = "com.springboot.mapper")
@EnableAspectJAutoProxy
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

3、自定义数据源切换注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(value = {ElementType.TYPE, ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface DB {
    String value();
}

4、定义切面使注解生效

package com.springboot.aop;

import com.springboot.config.DataSourceHelper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
public class DBAop {

    // @annotation 表示注解用到方法上,@within 则是类上
    @Pointcut(value = "@annotation(com.springboot.aop.DB) || @within(com.springboot.aop.DB)")
    public void pointcut() {}


    @Around(value = "pointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        // 获取方法上的自定义数据源注解
        DB annotation = method.getAnnotation(DB.class);
        if (annotation == null) {
            annotation = method.getDeclaringClass().getAnnotation(DB.class);
        }

        try {
            if (annotation != null) {
                DataSourceHelper.set(annotation.value());
            }
            return pjp.proceed();
        } finally {
            DataSourceHelper.remove();
        }
    }
}

5、使用注解

// 阿里数据库用户信息

@Service
@Slf4j
public class UserServiceImpl implements UserService {
    @Resource
    private UserMapper userMapper;
    @Resource
    private UserInfoService userInfoService;

    @Override
    @DB("aliDB")
    public List<User> list() {
        List<User> users = userMapper.list();
        List<User> userInfo = getUserInfo();
        users.addAll(userInfo);
        return users;
    }

    @DB("localDB")
    public List<User> getUserInfo() {
        List<UserInfo> list =  userInfoService.list();
        return list.stream().map(em -> {
            User user = new User();
            BeanUtils.copyProperties(em, user);
            return user;
        }).collect(Collectors.toList());
    }

}



// 本地用户数据库信息
@Service
@DB("localDB")
public class UserInfoServiceImpl implements UserInfoService {
    @Resource
    private UserInfoMapper userInfoMapper;

    @Override
    public List<UserInfo> list() {
        return userInfoMapper.list();
    }
}