一. 簡介:
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.運行結果,可以看到成功運行


在控制台輸出:

