一. 简介:
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.运行结果,可以看到成功运行
在控制台输出: