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