SpringBoot之多数据源切换


一. 简介:

 1. 什么是多数据源?

  多数据源指的是如果一个web项目用到了多个数据库,那么就需要连接多个数据库操作,这就是多数据源.

 2. 为什么切换数据源?

  项目中经常会遇到一个项目会用好几个数据库的情况,这个时候就需要我们进行多数据源配置.

二. 实例代码.

  切换数据源使用到了:AOP,自定义注解,以及SpringBoot项目

   表结构如下:

查看代码

CREATE TABLE `user` (
  `id` int(11) DEFAULT NULL,
  `username` varchar(10) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `goods` (
  `id` int(11) DEFAULT NULL,
  `goodname` varchar(10) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

  1.创建SpringBoot项目,并引入依赖。

查看代码

<!-- mybatis整合包 -->
     <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>
        <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.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

  2.在配置文件中,配置主从数据库

查看代码

# spring 数据库配置
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      # 主数据库
      master:
      	# 这里一定不能是 url
      	# 因为连接池的实际类型没有被公开,所以在您的自定义数据源的元数据中没有生成密钥,
        # 而且在IDE中没有完成(因为DataSource接口没有暴露属性)。另外,如果您碰巧在类路径上有Hikari,
       	# 那么这个基本设置就不起作用了,因为Hikari没有url属性(但是确实有一个jdbcUrl属性)。在这种情况下,您必须重写您的配置如下
        jdbc-url: jdbc:mysql://localhost:3306/user?serverTimezone=UTC
        username: root
        password: root
      # 从数据库
      slave:
        # 从数据源开关,默认关闭
        enabled: true
        # 这里一定不能是 url
        jdbc-url: jdbc:mysql://localhost:3306/goods?serverTimezone=UTC
        username: root
        password: root
        
# mybatis 配置
mybatis:
  type-aliases-package: cn.datasource.entity
  configuration:
    map-underscore-to-camel-case: true

  3.在项目包下创建 data 包,包下创建数据源枚举类      

查看代码

package cn.datasource.data;

/**
 * @author 南一
 * @description 类描述
 * @date 2021-12-08 15:37
 */
public enum DataSourceType {
    MASTER,
    SLAVE
}

   4. 在data包下创建切换数据源处理类 

查看代码

package cn.datasource.data;

/**
 * @author 南一
 * @description 类描述
 * @date 2021-12-08 15:38
 */
public class DynamicDataSourceContextHolder {

    /**
     * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
     * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
     */
    private final static ThreadLocal<String> LOCAL = new ThreadLocal<>();

    /**
     * 设置数据源变量
     *
     * @param dataSource
     */
    public static void setLocal(String dataSource){
        System.out.println("切换数据源" + dataSource);
        LOCAL.set(dataSource);
    }

    /**
     * 获取数据源变量
     */
    public static String getLocal(){
        return LOCAL.get();
    }

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

}

   5.创建一个类让它继承AbstractRoutingDataSource,并重写determineCurrentLookupKey方法。

查看代码

package cn.datasource.data;

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

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

/**
 * @author 南一
 * @description 类描述
 * @date 2021-12-08 15:41
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    /**
     * 设置默认属性
     *
     * @param defaultDataSource 默认数据源
     * @param dataSources 用来存放要切换的数据源
     */
    public DynamicDataSource(DataSource defaultDataSource, Map<Object,Object> dataSources) {
        super.setDefaultTargetDataSource(defaultDataSource);
        super.setTargetDataSources(dataSources);
        super.afterPropertiesSet();
    }

    /**
     * 用于决定用哪个数据库
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getLocal();
    }
}

   6.创建配置类,注入主从数据源和DynamicDataSource类。

查看代码

package cn.datasource.config;

import cn.datasource.data.DataSourceType;
import cn.datasource.data.DynamicDataSource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
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.Primary;

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

/**
 * @author 南一
 * @description 类描述
 * @date 2021-12-08 15:44
 */
@Configuration
public class DataSourceConfig {

    /**
     * 主数据库
     * @return
     */
    @ConfigurationProperties("spring.datasource.druid.master")
    @Bean
    public DataSource defaultDataSource(){
        return DataSourceBuilder.create().build();
    }

    /**
     * 从数据库
     * @return
     */
    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave",name = "enable",havingValue = "true")
    public DataSource slaveDataSource(){
        return DataSourceBuilder.create().build();
    }

    /**
     * @param defaultDataSource
     *          主
     *         想引用配置好的类,那么参数名就必须和方法名相同
     * @param slaveDataSource
     *          从
     *          想引用配置好的类,那么参数名就必须和方法名相同
     * @return
     */
    @Bean
    @Primary
    public DynamicDataSource dataSource(DataSource defaultDataSource,DataSource slaveDataSource){
        Map<Object,Object> map = new HashMap<>();
        map.put(DataSourceType.MASTER.name(),defaultDataSource);
        map.put(DataSourceType.SLAVE.name(),slaveDataSource);
        return new DynamicDataSource(defaultDataSource,map);
    }
}

   7.自定义注解,该注解用于决定你使用哪个数据库

查看代码

package cn.datasource.anno;

import cn.datasource.data.DataSourceType;

import java.lang.annotation.*;

/**
 * @author 南一
 * @description 类描述
 * @date 2021-12-08 15:51
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    DataSourceType value() default  DataSourceType.MASTER;
}

   8.创建AOP,拦截自定义注解。

查看代码

package cn.datasource.aop;

import cn.datasource.anno.DataSource;
import cn.datasource.data.DynamicDataSourceContextHolder;
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.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @author 南一
 * @description 类描述
 * @date 2021-12-08 15:53
 */
@Aspect
@Component
public class DataSourceAspect {

    @Pointcut("@annotation(cn.datasource.anno.DataSource)")
    public void pointcut(){}

    /**
     * 通过反射获取注解中值
     * @param pjp
     * @return
     * @throws Throwable
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        DataSource annotation = method.getAnnotation(DataSource.class);
        if(annotation!=null){
            DynamicDataSourceContextHolder.setLocal(annotation.value().name());
        }
        try {
            return pjp.proceed();
        }finally {
            /*移除当前线程存储的 Value 值。当 ThreadLocal 不在使用,最好在 finally 语句
            块中,调用 remove() 方法,释放去 Value 的引用,避免内存泄露。*/
            DynamicDataSourceContextHolder.removeLocal();
        }
    }
}

   9.在项目中创建实体类,DAO层,service层.

查看代码

// 实体类
package cn.datasource.pojo;

//  user 
public class User {

  private Integer id;
  private String username;


  public Integer getId() {
    return id;
  }

  public void setId(Integer id) {
    this.id = id;
  }


  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

}


package cn.datasource.pojo ;

// goods
public class Goods {

  private Integer id;
  private String goodname;


  public Integer getId() {
    return id;
  }

  public void setId(Integer id) {
    this.id = id;
  }


  public String getGoodname() {
    return goodname;
  }

  public void setGoodname(String goodname) {
    this.goodname = goodname;
  }

}

																	
// UserMapper
package cn.datasource.dao;

import cn.datasource.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

/**
 * @author 南一
 * @description 类描述
 * @date 2021-12-08 15:58
 */
@Mapper
public interface UserMapper {
    @Select("select * from user")
    List<User> getAll();
}


// GoodsMapper
package cn.datasource.dao;

import cn.datasource.pojo.Goods;
import cn.datasource.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

/**
 * @author 南一
 * @description 类描述
 * @date 2021-12-08 15:58
 */
@Mapper
public interface GoodsMapper {
    @Select("select * from goods")
    List<Goods> getAll();
}


															

// UserService
package cn.datasource.service;

import cn.datasource.pojo.User;

import java.util.List;

/**
 * @author 南一
 * @description 类描述
 * @date 2021-12-08 16:01
 */
public interface UserService {

    List<User> getAll();

}


// GoodsService
package cn.datasource.service;

import cn.datasource.pojo.Goods;
import cn.datasource.pojo.User;

import java.util.List;

/**
 * @author 南一
 * @description 类描述
 * @date 2021-12-08 16:01
 */
public interface GoodsService {
    List<Goods> getAll();
}

   10.编写Controller,测试

查看代码

package cn.datasource.controller;

import cn.datasource.anno.DataSource;
import cn.datasource.data.DataSourceType;
import cn.datasource.service.GoodsService;
import cn.datasource.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @author 南一
 * @description 类描述
 * @date 2021-12-08 16:04
 */
@Controller
public class DataTest {

    @Autowired
    private UserService userService;

    @Autowired
    private GoodsService goodsService;

    @RequestMapping("user")
    @ResponseBody
    @DataSource
    public String user(){
        userService.getAll().forEach(System.out::println);
        return "user";
    }

    @RequestMapping("goods")
    @ResponseBody
    @DataSource(DataSourceType.SLAVE)
    public String goods(){
        goodsService.getAll().forEach(System.out::println);
        return "goods";
    }

}

   11. 最后一定要在配置类,关闭 spring 的自动数据源配置

package cn.datasource;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class DatasourceApplication {

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

}

   12.运行结果,可以看到成功运行

   

 

         

        在控制台输出:

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM