spring boot使用AbstractRoutingDataSource实现动态数据源切换
1.请参见项目代码地址请参看博客开源框架RuoYi-Vue学习之基础框架搭建(集成mybatis实现基本增删改查以及PageHelper分页)1.Spring boot提供了AbstractRoutingDataSource根据用户定义的规则选择当前的数据源,这样我们可以在执行查询之前,设置使用的数据源。实现可动态路由的数据源,在每次数据库查询操作前执行。它的抽象方法determineCurren
·
1.请参见项目代码地址
一.基本增删改查框架搭建
请参看博客开源框架RuoYi-Vue学习之基础框架搭建(集成mybatis实现基本增删改查以及PageHelper分页)
二.AbstractRoutingDataSource
1.Spring boot提供了AbstractRoutingDataSource根据用户定义的规则选择当前的数据源,这样我们可以在执行查询之前,设置使用的数据源。实现可动态路由的数据源,在每次数据库查询操作前执行。它的抽象方法determineCurrentLookupKey()决定使用哪个数据源。
2.org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource源码的介绍:
3.AbstractRoutingDataSource的多数据源动态切换的核心逻辑是:在程序运行时,把数据源数据源通过 AbstractRoutingDataSource 动态织入到程序中,灵活的进行数据源切换。基于AbstractRoutingDataSource的多数据源动态切换,可以实现读写分离,这么做缺点也很明显,无法动态的增加数据源。
实现逻辑:
- 定义DynamicDataSource类继承抽象类AbstractRoutingDataSource,并实现了determineCurrentLookupKey()方法。
- 把配置的多个数据源会放在AbstractRoutingDataSource的 targetDataSources和defaultTargetDataSource中,然后通过afterPropertiesSet()方法将数据源分别进行复制到resolvedDataSources和resolvedDefaultDataSource中。
- 调用AbstractRoutingDataSource的getConnection()的方法的时候,先调用determineTargetDataSource()方法返回DataSource在进行getConnection()。
三.实现动态数据源切换步骤
3.1 自定义多数据源切换注解@DataSource以及数据源名称枚举类DataSourceType
/**
* 自定义多数据源切换注解
* 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
/**
* 切换数据源名称
*/
DataSourceType value() default DataSourceType.MASTER;
}
/**
* 数据源名称
*/
public enum DataSourceType {
/**
* 主库
*/
MASTER,
/**
* 从库
*/
SLAVE
}
3.2 编写数据源切换处理类DynamicDataSourceContextHolder
/**
* 数据源切换处理
*/
@Slf4j
public class DynamicDataSourceContextHolder {
/**
* 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 设置数据源的变量
*/
public static void setDataSourceType(String dataSourceType) {
log.info("切换到{}数据源", dataSourceType);
CONTEXT_HOLDER.set(dataSourceType);
}
/**
* 获得数据源的变量
*/
public static String getDataSourceType() {
return CONTEXT_HOLDER.get();
}
/**
* 清空数据源变量
*/
public static void clearDataSourceType() {
CONTEXT_HOLDER.remove();
}
}
3.3 编写DynamicDataSource扩展AbstractRoutingDataSource抽象类,重写determineCurrentLookupKey方法
/**
* 扩展Spring的AbstractRoutingDataSource抽象类,重写determineCurrentLookupKey方法。指定默认目标数据源以及指定目标数据源的映射map
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 指定默认目标数据源以及指定目标数据源的映射map(将lookup作为key)
* @param defaultTargetDataSource
* @param targetDataSources
*/
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
@Nullable
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
3.3 编写druid配置多数据源
/**
* druid 配置多数据源
*/
@Configuration
@Slf4j
public class DynamicDruidDataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.druid.slave")
public DataSource slaveDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "dynamicDataSource")
@Primary
public DynamicDataSource dataSource(DataSource masterDataSource, DataSource slaveDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
targetDataSources.put(DataSourceType.SLAVE.name(), slaveDataSource);
return new DynamicDataSource(masterDataSource, targetDataSources);
}
}
3.4 编写配置文件
##数据源配置
#1.使用最新的mysql连接驱动;2.访问数据库时无法识别时区(在中国可以选择Asia/Shanghai或者Asia/Hongkong,UTC会比中国时间早8个小时)
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#主库数据源
spring.datasource.druid.master.url=jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.druid.master.username=root
spring.datasource.druid.master.password=zaq12wsx
#从库数据源
spring.datasource.druid.slave.url=jdbc:mysql://localhost:3306/mashirro?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.druid.slave.username=root
spring.datasource.druid.slave.password=zaq12wsx
##项目相关配置
##Spring配置
#要激活某个profile,需要做的事就是将profile名称的列表赋值给spring.profiles.active属性。
spring.profiles.active=datasource
##开发环境配置
#服务器的HTTP端口
server.port=8088
#应用的访问路径
server.servlet.context-path=/
##日志配置
logging.level.com.mashirro=debug
logging.level.org.springframework=warn
##mybatis相关配置
#配置mapper映射xml路径(*通配符)
mybatis.mapper-locations=classpath:/mapper/*.xml
#加载全局的配置文件
mybatis.config-location=classpath:mybatis/mybatis-config.xml
#别名
#mybatis.type-aliases-package=com.mashirro.**.domain
##PageHelper分页插件配置(请参看官方文档https://pagehelper.github.io/docs/)
#分页插件会自动检测当前的数据库链接,自动选择合适的分页方式。 你可以配置helperDialect属性来指定分页插件使用哪种方言。
pagehelper.helper-dialect=mysql
#分页合理化参数,默认值为false。当该参数设置为true时,pageNum<=0 时会查询第一页,pageNum>pages(超过总数时),会查询最后一页。默认false 时,直接根据参数进行查询。
pagehelper.reasonable=true
#为了支持startPage(Object params)方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值,可以配置 pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值,默认值为pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero
pagehelper.params=count=countSql
#支持通过Mapper接口参数来传递分页参数,默认值false,分页插件会从查询方法的参数值中,自动根据上面params配置的字段中取值,查找到合适的值时就会自动分页。
pagehelper.support-methods-arguments=true
3.5 使用Spring Aop编写切面处理类–>多数据源处理
@Aspect
@Component
@Slf4j
public class DataSourceAspect {
/**
* 声明一个切入点
*
* @within:对象级别,用于拦截标注在类上面的注解
* @annotation:方法级别,用于拦截标注在方法上面的注解
*/
@Pointcut("@annotation(com.mashirro.dynamicdatasource.annotation.TargetDataSource)"
+ "|| @within(com.mashirro.dynamicdatasource.annotation.TargetDataSource)") //切入点表达式
public void dsPointCut() {
}
/**
* 环绕通知:
* 通知方法的第一个参数必须是ProceedingJoinPoint类型。
* 在通知正文中,对ProceedingJoinPoint调用proceed()将导致底层方法执行。around通知返回的值是方法调用方看到的返回值。
*/
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
TargetDataSource targetDataSource = getTargetDataSourceAnnotation(point);
if (targetDataSource != null) {
DynamicDataSourceContextHolder.setDataSourceType(targetDataSource.value().name());
}
try {
return point.proceed();
} finally {
// 销毁数据源 在执行方法之后
DynamicDataSourceContextHolder.clearDataSourceType();
log.info("clear datasource");
}
}
/**
* 获取需要切换的数据源
*/
public TargetDataSource getTargetDataSourceAnnotation(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
TargetDataSource targetDataSource = AnnotationUtils.findAnnotation(signature.getMethod(), TargetDataSource.class);
if (Objects.nonNull(targetDataSource)) {
return targetDataSource;
}
return AnnotationUtils.findAnnotation(signature.getDeclaringType(), TargetDataSource.class);
}
}
3.6 启动类上排除数据源自动配置
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
3.7 使用postman测试成功!
四.优化-连接池配置
4.1 编写druid连接池配置类
##连接池配置
# 初始连接数
spring.datasource.druid.initialSize=5
# 最小连接池数量
spring.datasource.druid.minIdle=10
# 最大连接池数量
spring.datasource.druid.maxActive=20
# 配置获取连接等待超时的时间
spring.datasource.druid.maxWait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.druid.timeBetweenEvictionRunsMillis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.druid.minEvictableIdleTimeMillis=300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
spring.datasource.druid.maxEvictableIdleTimeMillis=900000
# 配置检测连接是否有效,,要求是一个查询语句,常用select 'x'.如果validationQuery为null,testOnBorrow和testOnReturn和testWhileIdle都不会起作用。
spring.datasource.druid.validationQuery=SELECT 1 FROM DUAL
# 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
spring.datasource.druid.testWhileIdle=true
# 申请连接时执行validationQuery检测连接是否有效,true会降低性能。
spring.datasource.druid.testOnBorrow=false
# 归还连接时执行validationQuery检测连接是否有效,true会降低性能。
spring.datasource.druid.testOnReturn=false
/**
* druid连接池配置属性
*
* @author ruoyi
*/
@Configuration
public class DruidProperties {
@Value("${spring.datasource.druid.initialSize}")
private int initialSize;
@Value("${spring.datasource.druid.minIdle}")
private int minIdle;
@Value("${spring.datasource.druid.maxActive}")
private int maxActive;
@Value("${spring.datasource.druid.maxWait}")
private int maxWait;
@Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")
private int timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.druid.minEvictableIdleTimeMillis}")
private int minEvictableIdleTimeMillis;
@Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}")
private int maxEvictableIdleTimeMillis;
@Value("${spring.datasource.druid.validationQuery}")
private String validationQuery;
@Value("${spring.datasource.druid.testWhileIdle}")
private boolean testWhileIdle;
@Value("${spring.datasource.druid.testOnBorrow}")
private boolean testOnBorrow;
@Value("${spring.datasource.druid.testOnReturn}")
private boolean testOnReturn;
public DruidDataSource dataSource(DruidDataSource datasource) {
/** 配置初始化大小、最小、最大 */
datasource.setInitialSize(initialSize);
datasource.setMaxActive(maxActive);
datasource.setMinIdle(minIdle);
/** 配置获取连接等待超时的时间 */
datasource.setMaxWait(maxWait);
/** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
/** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */
datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
/**
* 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
*/
datasource.setValidationQuery(validationQuery);
/** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */
datasource.setTestWhileIdle(testWhileIdle);
/** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
datasource.setTestOnBorrow(testOnBorrow);
/** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
datasource.setTestOnReturn(testOnReturn);
return datasource;
}
}
4.2 在配置多数据源bean时注入
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource(DruidProperties druidProperties) {
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
// return dataSource;
return druidProperties.dataSource(dataSource);
}
@Bean
@ConfigurationProperties("spring.datasource.druid.slave")
public DataSource slaveDataSource(DruidProperties druidProperties) {
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
// return dataSource;
return druidProperties.dataSource(dataSource);
}
4.3 测试生效!!
五.优化-配置druid监控(待补充…)
更多推荐
已为社区贡献4条内容
所有评论(0)