此项目也是借助网上各种双数据源动态切换改编的(参考人人代码开源),暂没考虑数据库事务。
1、首先引入各种jar,springboot、mybatisplus等,配置pom文件如下:
4.0.0
com.hualife
springboot
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-parent
2.7.5
1.8
2.2.2
1.4.5
1.2.22
2.11.0
1.10
8.0.30
1.2.13
3.5.2
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-web
com.github.pagehelper
pagehelper-spring-boot-starter
${pagehelper.spring.boot.version}
com.alibaba
druid
${druid.version}
mysql
mysql-connector-java
${mysql.version}
com.alibaba
druid-spring-boot-starter
1.2.4
druid
com.alibaba
com.baomidou
mybatis-plus-boot-starter
${mybatisplus.version}
com.baomidou
mybatis-plus-generator
jsqlparser
com.github.jsqlparser
mybatis
org.mybatis
org.projectlombok
lombok
1.16.22
org.apache.commons
commons-lang3
junit
junit
4.12
test
org.springframework.boot
spring-boot-maven-plugin
2.7.5
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> HOLDER = new ThreadLocal>() {
@Override
protected Deque initialValue() {
return new ArrayDeque();
}
};
/**
* 获得当前线程数据源
* @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 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 datasource = new LinkedHashMap<>();
public Map getDatasource() {
return datasource;
}
public void setDatasource(Map 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 dataSourcePropertiesMap = dynamicDataSourceProperties.getDatasource();
Map
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 - 鍗氬鍥�