目录
一、前言
本篇主要介绍springboot如何进行多数据源配置,及一些设计思路。
主要包含以下内容:
1、多数据源通过注解动态切换
2、通过配置文件开关,只启用一个数据源
3、springboot中使用多数据源存在循环引用的问题
二、数据准备
为了方便演示,在两台服务器上创建两个数据库,分别为master和slave
在master数据库创建表z_f_user,表结构及数据如上图。
在slave数据库创建表z_f_msg,表结构及数据如上图。
三、配置文件
1、pom依赖
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.11</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
</dependencies>
2、appication.properties配置
#服务端口
server.port=8080
#服务名称
spring.application.name=zhufeng-web-msg
#数据源公共配置
spring.datasource.druid.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.druid.driver-class-name=com.mysql.jdbc.Driver
# master数据源配置
spring.datasource.druid.dynamic.master.jdbc-url=jdbc:mysql://10.211.55.2:3306/master?serverTimezone=UTC&useUnicode=true@characterEncoding=utf-8
spring.datasource.druid.dynamic.master.username=root
spring.datasource.druid.dynamic.master.password=123
# slave数据源配置
spring.datasource.druid.dynamic.slave.jdbc-url=jdbc:mysql://10.211.55.14:3306/slave?serverTimezone=UTC&useUnicode=true@characterEncoding=utf-8
spring.datasource.druid.dynamic.slave.username=root
spring.datasource.druid.dynamic.slave.password=123
#是否启动slave数据源开关,true:启用。默认为false
spring.datasource.druid.dynamic.slave.enabled=true
mybatis.type-aliases-package=com.zhufeng.web.mapper
mybatis.mapper-locations=classpath:mapper/*.xml
四、源码实现
1、数据源注解
package com.zhufeng.base.db;
import java.lang.annotation.*;
/**
* @ClassName: DataSource
* @Description 数据源
* @author 月夜烛峰
* @date 2022/9/5 20:33
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
/**
*
* 切换数据源名称
*/
DataSourceType value() default DataSourceType.MASTER;
}
2、数据源枚举值
package com.zhufeng.base.db;
/**
* @ClassName: DataSourceType
* @Description 数据源枚举值
* @author 月夜烛峰
* @date 2022/9/5 20:33
*/
public enum DataSourceType {
MASTER,
SLAVE
}
3、配置多个数据源bean
package com.zhufeng.base.db;
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;
/**
* @ClassName: DataSourceConfig
* @Description 配置多数据源bean,默认使用master数据源
* @author 月夜烛峰
* @date 2022/9/5 20:33
*/
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.druid.dynamic.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.druid.dynamic.slave")
@ConditionalOnProperty(prefix = "spring.datasource.druid.dynamic.slave", name = "enabled", havingValue = "true")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "dynamicDataSource")
@Primary
public DynamicDataSource dataSource(DataSource masterDataSource,DataSource slaveDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>(8);
targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
targetDataSources.put(DataSourceType.SLAVE.name(), slaveDataSource);
return new DynamicDataSource(masterDataSource, targetDataSources);
}
}
4、切层注入
package com.zhufeng.base.db;
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.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* @ClassName: DataSourceAspect
* @Description spring切层注入
* @author 月夜烛峰
* @date 2022/9/5 20:33
*/
@Aspect
@Order(1)
@Component
public class DataSourceAspect {
@Pointcut("@annotation(com.zhufeng.base.db.DataSource)")
public void dsPointCut() {
}
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
DataSource dataSource = method.getAnnotation(DataSource.class);
if (dataSource != null) {
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
}
try {
return point.proceed();
} finally {
// 销毁数据源 在执行方法之后
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
}
5、创建DynamicDataSource
package com.zhufeng.base.db;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.Map;
/**
* @ClassName: DynamicDataSource
* @Description 动态数据源创建
* @author 月夜烛峰
* @date 2022/9/5 20:33
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
/**
* 根据Key获取数据源的信息
*
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
6、ThreadLocal临时存储数据源
package com.zhufeng.base.db;
/**
* @ClassName: DynamicDataSourceContextHolder
* @Description 数据源临时处理
* @author 月夜烛峰
* @date 2022/9/5 20:33
*/
public class DynamicDataSourceContextHolder {
/**
* 使用ThreadLocal临时存储
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 设置数据源变量
* @param dataSourceType
*/
public static void setDataSourceType(String dataSourceType){
CONTEXT_HOLDER.set(dataSourceType);
}
/**
* 获取数据源变量
* @return
*/
public static String getDataSourceType(){
return CONTEXT_HOLDER.get();
}
/**
* 清空数据源变量
*/
public static void clearDataSourceType(){
CONTEXT_HOLDER.remove();
}
}
7、Controller访问
package com.zhufeng.web.controller;
import com.alibaba.fastjson.JSONObject;
import com.zhufeng.base.db.DataSource;
import com.zhufeng.base.db.DataSourceType;
import com.zhufeng.web.service.MsgService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @ClassName: DataController
* @Description 多数据源切换演示
* @author 月夜烛峰
* @date 2022/9/5 20:40
*/
@RestController
@RequestMapping("data")
public class DataController {
@Resource
MsgService msgService;
@RequestMapping("master")
public JSONObject showUser(){
return msgService.showFirstUser();
}
@RequestMapping("slave")
public JSONObject showMsg(){
return msgService.showFirstMsg();
}
}
8、接口查询及数据获取
service接口:
/**
* @ClassName: MsgService
* @Description TODO
* @author 月夜烛峰
* @date 2022/9/5 20:06
*/
public interface MsgService {
/**
* 获取第一条消息信息
* @return
*/
JSONObject showFirstMsg();
/**
* 获取第一个用户信息
* @return
*/
JSONObject showFirstUser();
}
实现类:
package com.zhufeng.web.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.zhufeng.base.db.DataSource;
import com.zhufeng.base.db.DataSourceType;
import com.zhufeng.web.mapper.MsgMapper;
import com.zhufeng.web.service.MsgService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @ClassName: MsgServiceImpl
* @Description TODO
* @author 月夜烛峰
* @date 2022/9/5 15:06
*/
@Service
public class MsgServiceImpl implements MsgService {
@Resource
MsgMapper msgMapper;
/**
* 获取第一条消息信息
* @return
*/
@DataSource(value = DataSourceType.SLAVE)
@Override
public JSONObject showFirstMsg() {
return msgMapper.showFirstMsg();
}
/**
* 获取第一个用户信息
* @return
*/
@Override
public JSONObject showFirstUser() {
return msgMapper.showFirstUser();
}
}
dao层代码:
/**
* @ClassName: MsgMapper
* @Description TODO
* @author 月夜烛峰
* @date 2022/9/5 14:55
*/
@Mapper
public interface MsgMapper {
/**
* 获取第一条消息信息
* @return
*/
JSONObject showFirstMsg();
/**
* 获取第一个用户信息
* @return
*/
JSONObject showFirstUser();
}
查询语句:
<!--获取第一个用户信息-->
<select id="showFirstUser" resultType="com.alibaba.fastjson.JSONObject">
SELECT
*
FROM
z_f_user
LIMIT 0,1
</select>
<!--获取第一个消息信息-->
<select id="showFirstMsg" resultType="com.alibaba.fastjson.JSONObject">
SELECT
*
FROM
z_f_msg
LIMIT 0,1
</select>
8、启动类
/**
* @ClassName: MsgApplication
* @Description msg启动类
* @author 月夜烛峰
* @date 2022/7/22 08:53
*/
@EnableDiscoveryClient
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class MsgApplication {
public static void main(String[] args) {
SpringApplication.run(MsgApplication.class, args);
}
}
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})这里是启动时把DataSourceAutoConfiguration排除,避免在启动过程中出现循环引用问题
五、启动测试
1、访问master数据库
2、访问slave数据库
3、修改开关配置
在application.properties中将以下配置删除或者改为false,重新启动项目
#是否启动slave数据源开关,true:启用。默认为false
spring.datasource.druid.dynamic.slave.enabled=false
再次访问slave,报错如下:
控制台报错信息:
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: SELECT * FROM z_f_msg LIMIT 0,1
### Cause: java.sql.SQLSyntaxErrorException: Table 'master.z_f_msg' doesn't exist
; bad SQL grammar []; nested exception is java.sql.SQLSyntaxErrorException: Table 'master.z_f_msg' doesn't exist] with root cause
错误日志显示,去master数据源中查询z_f_msg,也就是没有切换到slave数据源,在servce中添加的slave数据源的动态切换注解没有生效,使用默认的master数据源。
@DataSource(value = DataSourceType.SLAVE)
@Override
public JSONObject showFirstMsg() {
return msgMapper.showFirstMsg();
}
master作为默认数据源配置,在初始化数据源时已经指定