此项目也是借助网上各种双数据源动态切换改编的(参考人人代码开源),暂没考虑数据库事务。
1、首先引入各种jar,springboot、mybatisplus等,配置pom文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.hualife</groupId>
<artifactId>springboot</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
</parent>
<properties>
<java.version>1.8</java.version>
<mybatis.spring.boot.version>2.2.2</mybatis.spring.boot.version>
<pagehelper.spring.boot.version>1.4.5</pagehelper.spring.boot.version>
<druid.version>1.2.22</druid.version>
<commons.io.version>2.11.0</commons.io.version>
<commons.configuration.version>1.10</commons.configuration.version>
<mysql.version>8.0.30</mysql.version>
<druid.version>1.2.13</druid.version>
<mybatisplus.version>3.5.2</mybatisplus.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper.spring.boot.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.4</version>
<exclusions>
<exclusion>
<artifactId>druid</artifactId>
<groupId>com.alibaba</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatisplus.version}</version>
<exclusions>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
</exclusion>
<exclusion>
<artifactId>jsqlparser</artifactId>
<groupId>com.github.jsqlparser</groupId>
</exclusion>
<exclusion>
<artifactId>mybatis</artifactId>
<groupId>org.mybatis</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.22</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.5</version>
</plugin>
</plugins>
</build>
</project>
2、在yml中配置数据源信息如下(可多配置一些数据库连接池信息,自行百度吧有很多可以借鉴):
server:
port: 8080
spring:
datasource:
dynamic:
datasource:
master:
#MySQL配置
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/DB1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
username: root
password:
initial-size: 2
max-active: 20
min-idle: 1
slave:
#MySQL配置
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/DB2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
username: root
password:
initial-size: 2
max-active: 20
min-idle: 1
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/**/*.xml
3、建数据库,建表(不会的百度吧),并且生成对应的Java实体及mapper及xml等(可使用开源版人人代码生成器)。
4、搭建springboot项目,项目结构如下图(除了config以外,其他的包应该都知道是啥):
5、在config目录下创建Java类,CurrDataSource.java(注解类,可用于项目中区分数据源),DataSourceAspect.java(切面类,根据切面选择不同的数据源),DataSourceFactory.java(数据源工厂,包含数据库连接池信息),DataSourceProperties.java(数据源信息封装的实体类),DBTypeName.java(数据源名称常量),DynamicContextHolder.java(数据源上下文切换用的,实际是队列),
DynamicDataSourceProperties.java(双数据源信息对应的map,具体看下文中的代码吧),MyDynamicDataSourceConfig.java(动态数据源配置类),MyRoutingDataSource.java(数据源路由类)
6、数据源切换的注解:
import java.lang.annotation.*;
/**
* 多数据源注解
* 自己项目中添加
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CurrDataSource {
String value() default "master";
}
7、aop切面类:
import lombok.extern.slf4j.Slf4j;
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.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 多数据源,切面处理类
*/
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
@Slf4j
public class DataSourceAspect {
@Pointcut("@annotation(com.hualife.modules.config.CurrDataSource) " //方法包含注解
+"|| @within(com.hualife.modules.config.CurrDataSource)" //类包含注解
+"|| within(com.baomidou.mybatisplus.core.mapper.BaseMapper+)")
// 切BaseMapper及其子类,因为在mapper加注解但调用父类的方法切不到,故加了within,这样就可以切到父类了
//其实只要within表达式就行,因为不管是service的impl还是mapper最终调的都是mapper的方法
public void dSPointCut() {
}
@Around("dSPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Class targetClass = point.getTarget().getClass();
Method method = signature.getMethod();
log.info("执行数据库操作的类是:{},函数是:{}", targetClass.getName(), method.getName());
// CurrDataSource targetDataSource = (CurrDataSource) targetClass.getAnnotation(CurrDataSource.class);
CurrDataSource targetDataSource = AnnotationUtils.findAnnotation(targetClass, CurrDataSource.class);//获取目标类上注解
CurrDataSource methodDataSource = method.getAnnotation(CurrDataSource.class);//获取目标函数上注解
if (targetDataSource != null || methodDataSource != null) {
log.info(targetDataSource + "===========" + methodDataSource);
String value;
if (methodDataSource != null) {
value = methodDataSource.value();
} else {
value = targetDataSource.value();
}
DynamicContextHolder.push(value);
} else {
log.info("执行数据库操作的类及其函数上没有@CurrDataSource,而这个执行数据库操作的函数属于BaseMapper及其子类下的函数,故调用master");
DynamicContextHolder.push(DBTypeName.MASTER);
}
try {
return point.proceed();
} finally {
DynamicContextHolder.poll();
log.info("clean datasource");
}
}
}
8、数据源工厂:
import com.alibaba.druid.pool.DruidDataSource;
import java.sql.SQLException;
/**
* DruidDataSource
*/
public class DataSourceFactory {
public static DruidDataSource buildDruidDataSource(DataSourceProperties properties) {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(properties.getDriverClassName());
druidDataSource.setUrl(properties.getUrl());
druidDataSource.setUsername(properties.getUsername());
druidDataSource.setPassword(properties.getPassword());
druidDataSource.setInitialSize(properties.getInitialSize());
druidDataSource.setMaxActive(properties.getMaxActive());
druidDataSource.setMinIdle(properties.getMinIdle());
druidDataSource.setMaxWait(properties.getMaxWait());
druidDataSource.setTimeBetweenEvictionRunsMillis(properties.getTimeBetweenEvictionRunsMillis());
druidDataSource.setMinEvictableIdleTimeMillis(properties.getMinEvictableIdleTimeMillis());
druidDataSource.setMaxEvictableIdleTimeMillis(properties.getMaxEvictableIdleTimeMillis());
druidDataSource.setValidationQuery(properties.getValidationQuery());
druidDataSource.setValidationQueryTimeout(properties.getValidationQueryTimeout());
druidDataSource.setTestOnBorrow(properties.isTestOnBorrow());
druidDataSource.setTestOnReturn(properties.isTestOnReturn());
druidDataSource.setPoolPreparedStatements(properties.isPoolPreparedStatements());
druidDataSource.setMaxOpenPreparedStatements(properties.getMaxOpenPreparedStatements());
druidDataSource.setSharePreparedStatements(properties.isSharePreparedStatements());
try {
// druidDataSource.setFilters(properties.getFilters());
druidDataSource.init();
} catch (SQLException e) {
e.printStackTrace();
}
return druidDataSource;
}
}
9、多数据源属性类:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 多数据源属性,yml
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DataSourceProperties {
private String driverClassName;
private String url;
private String username;
private String password;
/**
* Druid默认参数
*/
private int initialSize = 2;
private int maxActive = 10;
private int minIdle = -1;
private long maxWait = 60 * 1000L;
private long timeBetweenEvictionRunsMillis = 60 * 1000L;
private long minEvictableIdleTimeMillis = 1000L * 60L * 30L;
private long maxEvictableIdleTimeMillis = 1000L * 60L * 60L * 7;
private String validationQuery = "select 1";
private int validationQueryTimeout = -1;
private boolean testOnBorrow = false;
private boolean testOnReturn = false;
private boolean testWhileIdle = true;
private boolean poolPreparedStatements = false;
private int maxOpenPreparedStatements = -1;
private boolean sharePreparedStatements = false;
private String filters = "stat,wall";
}
10、数据库名字常量:
/**
* 数据库名字常量
*/
public class DBTypeName {
public static final String MASTER = "master";
public static final String SLAVE = "slave";
}
11、多数据源上下文 ,用来放置数据源名字:
import java.util.ArrayDeque;
import java.util.Deque;
/**
* 多数据源上下文
* 用来放置数据源
*/
public class DynamicContextHolder {
private static final ThreadLocal<Deque<String>> HOLDER = new ThreadLocal<Deque<String>>() {
@Override
protected Deque<String> initialValue() {
return new ArrayDeque<String>();
}
};
/**
* 获得当前线程数据源
* @return 数据源名称
*/
public static String peek() {
return HOLDER.get().peek();
}
/**
* 设置当前线程数据源
* @param dataSource 数据源名称
*/
public static void push(String dataSource) {
HOLDER.get().push(dataSource);
}
/**
* 清空当前线程数据源
*/
public static void poll() {
Deque<String> deque = HOLDER.get();
deque.poll();
if (deque.isEmpty()) {
HOLDER.remove();
}
}
}
12、多数据源属性类(把yml中多数据源信息对应成map):
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 多数据源属性
*/
@ConfigurationProperties(prefix = "spring.datasource.dynamic")
public class DynamicDataSourceProperties {
private Map<String, DataSourceProperties> datasource = new LinkedHashMap<>();
public Map<String, DataSourceProperties> getDatasource() {
return datasource;
}
public void setDatasource(Map<String, DataSourceProperties> datasource) {
this.datasource = datasource;
}
}
13、多数据源动态切换配置类:
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* 配置多数据源
*/
@Slf4j
@Configuration
@EnableConfigurationProperties(DynamicDataSourceProperties.class)
public class MyDynamicDataSourceConfig {
@Autowired
private DynamicDataSourceProperties dynamicDataSourceProperties;
@Bean(name = "dataSourceProperties")
@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.master")
public DataSourceProperties dataSourceProperties() {
return new DataSourceProperties();
}
@Bean
public MyRoutingDataSource dynamicDataSource(@Qualifier("dataSourceProperties") DataSourceProperties dataSourceProperties) {
MyRoutingDataSource dynamicDataSource = new MyRoutingDataSource();
Map<String, DataSourceProperties> dataSourcePropertiesMap = dynamicDataSourceProperties.getDatasource();
Map<Object, Object> targetDataSources = new HashMap<>(dataSourcePropertiesMap.size());
dataSourcePropertiesMap.forEach((k, v) -> {
DruidDataSource druidDataSource = DataSourceFactory.buildDruidDataSource(v);
targetDataSources.put(k, druidDataSource);
});
dynamicDataSource.setTargetDataSources(targetDataSources);
//默认数据源
DruidDataSource defaultDataSource = DataSourceFactory.buildDruidDataSource(dataSourceProperties);
dynamicDataSource.setDefaultTargetDataSource(defaultDataSource);
return dynamicDataSource;
}
@Bean("sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSourceProperties") DataSourceProperties dataSourceProperties) throws Exception {
MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
sqlSessionFactory.setDataSource(dynamicDataSource(dataSourceProperties));
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.setJdbcTypeForNull(JdbcType.NULL);
configuration.setMapUnderscoreToCamelCase(true);
configuration.setCacheEnabled(false);
sqlSessionFactory.setConfiguration(configuration);
return sqlSessionFactory.getObject();
}
}
14、多数据源路由:
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 多数据源
*/
@Slf4j
public class MyRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
String dataSource = DynamicContextHolder.peek();
log.info("使用数据源:{}", dataSource);
return dataSource;
}
}
15、多数据源路由理解:参考下图(借用别人的图)銆怉bstractRoutingDataSource銆戝垎鏋� - zzsuje - 鍗氬鍥�