化妆品做备案的网站,网页模版之家,农村自建房设计图一层半,抖音网络营销案例使用AbstractRoutingDataSource实现多数据源 与 获取mapper上注解
背景 随着业务发展速度越来越快#xff0c;数据的增长也呈现倍数级别增长#xff0c;数据库的压力#xff0c;对于查询和写入等所有操作#xff0c;都依赖于主库#xff0c;其实有一些对于时效性要求不高…使用AbstractRoutingDataSource实现多数据源 与 获取mapper上注解
背景 随着业务发展速度越来越快数据的增长也呈现倍数级别增长数据库的压力对于查询和写入等所有操作都依赖于主库其实有一些对于时效性要求不高的场景无需使用主库查询可以使用从库来分摊主库的压力提升数据库集群整体的吞吐量
处理思路
其实处理思路共有两种
1. 我们创建两个数据源不同的数据源扫描不同的包. 也就是查询从库或者查询主库是通过不同的mapper文件物理隔离的优势就是开发简单快速搭建不用担心侵入原有逻辑。缺点也是显而易见的如果一个sql在某些场景下需要主库查询另外场景需要从库查询那么我们要写两套sql如果有新增字段等ddl操作时我们要改两遍sql由于比较简单本文不做描述。
2. 使用动态数据源配置 在多个数据源之上抽象出来一个虚拟的动态数据源等到执行sql的前一步我们才会选择哪个数据源执行优点是能够更精细化的管控sql执行缺点的话就是开发稍微复杂些
动态数据源的执行逻辑
我们需要先了解下spring的AbstractRoutingDataSource spring提供了一套数据库路由方案我们重写determineCurrentLookupKey 方法将想要的数据库返回即可。
以spring boot 为例
一、 mysql的基本配置 首先配置主库和从库的数据源这个是前提 Bean(name mysqlDataSource)
Primary
public DataSource mysqlDataSource() {DruidDataSource dataSource new DruidDataSource();dataSource.setDriverClassName(driverClass);dataSource.setUrl(url);dataSource.setUsername(user);dataSource.setPassword(password);dataSource.setMaxActive(300);dataSource.setKeepAlive(true);dataSource.setMaxWait(60000);dataSource.setValidationQuery(SELECT x);dataSource.setMinEvictableIdleTimeMillis(300000);dataSource.setTestWhileIdle(true);dataSource.setTestOnBorrow(true);dataSource.setTestOnReturn(false);return dataSource;
}
/*** 从库主库数据源*/
Bean(name mysqlSlaveDataSource)
public DataSource mysqlSlaveDataSource() {DruidDataSource dataSource new DruidDataSource();dataSource.setDriverClassName(driverClass);dataSource.setUrl(url);dataSource.setUsername(readUser);dataSource.setPassword(passwordRead);dataSource.setMaxActive(300);dataSource.setKeepAlive(true);dataSource.setMaxWait(60000);dataSource.setValidationQuery(SELECT x);dataSource.setMinEvictableIdleTimeMillis(300000);dataSource.setTestWhileIdle(true);dataSource.setTestOnBorrow(true);dataSource.setTestOnReturn(false);return dataSource;
}配置动态数据源将多数据源注入进动态数据源并设置默认的数据源 Bean(name dynamicDataSource)
public DynamicDataSource dynamicDataSource(Qualifier(mysqlDataSource) DataSource mysqlDataSource,Qualifier(mysqlSlaveDataSource) DataSource mysqlSlaveDataSource) {MapObject, Object targetDataSources Maps.newHashMap();targetDataSources.put(DataSourceEnum.MASTER, mysqlDataSource);targetDataSources.put(DataSourceEnum.SLAVE, mysqlSlaveDataSource);return new DynamicDataSource(mysqlDataSource, targetDataSources);
}将动态数据源设置到sql的sessionFactory Bean(name mysqlSqlSessionFactory)
Primary
public SqlSessionFactory masterSqlSessionFactory(Qualifier(dynamicDataSource) DataSource dynamicDataSource) throws Exception {final SqlSessionFactoryBean sessionFactory new SqlSessionFactoryBean();sessionFactory.setDataSource(dynamicDataSource);sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MysqlDataSourceConfig.MAPPER_LOCATION));// 配置org.apache.ibatis.session.Configuration configuration new org.apache.ibatis.session.Configuration();configuration.setMapUnderscoreToCamelCase(true);configuration.setDefaultStatementTimeout(30);configuration.addInterceptor(new MybatisUMPInterceptor());sessionFactory.setConfiguration(configuration);return sessionFactory.getObject();
}二、 新增的配置
新增加库的枚举定义
package com.common.enums;/*** 数据源配置枚举** author wangzym*/
public enum DataSourceEnum {/*** 使用主库*/MASTER(1, 主库),/*** 使用从库*/SLAVE(2, 从库);/*** /*** 编码*/private Integer code;/*** 描述*/private String desc;DataSourceEnum(Integer code, String desc) {this.code code;this.desc desc;}public Integer getCode() {return this.code;}public String getDesc() {return this.desc;}public static DataSourceEnum getByCode(Integer code) {if (code null) {return null;}for (DataSourceEnum dataSourceEnum : DataSourceEnum.values()) {if (dataSourceEnum.getCode().equals(code)) {return dataSourceEnum;}}return null;}
}动态数据源的定义需要继承spring的AbstractRoutingDataSource
package com.test.datasource;import com.alibaba.fastjson.JSON;
import com.test.SettleCheckException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import javax.sql.DataSource;
import java.util.Map;/*** 动态切换数据源类** author wangzym*/
Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {public DynamicDataSource(DataSource defaultTargetDataSource, MapObject, Object targetDataSources) {//不符合配置条件直接中断启动流程if (defaultTargetDataSource null || targetDataSources null || targetDataSources.isEmpty()) {throw new SettleCheckException(默认数据源和可切换的数据源不允许为空!请检查 spring-config-datasource-druid.xml - dynamicDataSource 对应配置);}//设置默认数据源super.setDefaultTargetDataSource(defaultTargetDataSource);//设置用于切换的数据源super.setTargetDataSources(targetDataSources);//初始化本地的数据源记录super.afterPropertiesSet();}Overrideprotected Object determineCurrentLookupKey() {log.debug(当前线程使用SQL的数据库是:{}, JSON.toJSONString(DynamicDataSourceHolder.getDataSource()));return DynamicDataSourceHolder.getDataSource();}}新增注解 package com.test.datasource;import com.test.DataSourceEnum;import java.lang.annotation.*;/*** 切换数据源注解默认连接主库* 要在mapper上加此注解才可以* p*/
Inherited
Retention(RetentionPolicy.RUNTIME)
Target(ElementType.METHOD)
Documented
public interface SwitchDataSource {DataSourceEnum name() default DataSourceEnum.MASTER;
}拦截器改造需要拦截sql此方法可以直接拦截sql执行此方法要重点理解是mybatis的拦截方案其中Intercepts可以通过不同的类型来进行不同的拦截此处拦截所有执行的sql直接获取mapper上的注解会获取不到需要通过反射的方式来获取需要重点注意此处不是本文的重点有兴趣可以了解mybatis源码。 package com.test.interceptor;import com.test.DuccConstants;
import com.test.enums.DataSourceEnum;
import com.test.datasource.DynamicDataSourceHolder;Intercepts({Signature(type Executor.class, method query, args {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),Signature(type Executor.class, method query, args {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),Signature(type Executor.class, method update, args {MappedStatement.class, Object.class}),})
Slf4j
public class MybatisUMPInterceptor implements Interceptor {Value(${test.current.env:pre})private static String env;static {env System.getProperty(current.env, pre);log.info(当前切面的环境为:{}, env);}Overridepublic Object intercept(Invocation invocation) throws Throwable {CallerInfo callerInfo null;try {//1.处理数据源切换SwitchDataSource chooseDataSource getSwitchDbAnnotation((MappedStatement) invocation.getArgs()[0]);if (null ! chooseDataSource DuccConstants.allowedMultiDb()) {//默认会使用MASTER数据源 如果开启事务了,而且当前数据源不是MASTER会强制切换为MASTERDynamicDataSourceHolder.setDataSource(chooseDataSource.name());if (TransactionSynchronizationManager.isActualTransactionActive() DataSourceEnum.SLAVE.equals(chooseDataSource.name())) {DynamicDataSourceHolder.setDataSource(DataSourceEnum.MASTER);}} else {DynamicDataSourceHolder.setDataSource(DataSourceEnum.MASTER);}//2.埋点监控Object[] args invocation.getArgs();MappedStatement ms (MappedStatement) args[0];//ump key可以自定义String jKey String.format(%s_MyBatis.%s, env, ms.getId());if (DynamicDataSourceHolder.getDataSource() ! null DynamicDataSourceHolder.getDataSource().equals(DataSourceEnum.SLAVE)) {jKey jKey _slave;}callerInfo Profiler.registerInfo(jKey, false, true);} catch (Exception ignore) {//ignorelog.warn(切面执行出现异常:{}, ignore.getStackTrace(), ignore);}try {return invocation.proceed();} catch (Throwable e) {try {if (null ! callerInfo) {Profiler.functionError(callerInfo);}} catch (Exception ignore) {//ignore}throw e;} finally {try {if (null ! callerInfo) {Profiler.registerInfoEnd(callerInfo);}} catch (Exception ignore) {//ignore}}}private SwitchDataSource getSwitchDbAnnotation(MappedStatement mappedStatement) {SwitchDataSource annotation null;try {String id mappedStatement.getId();String className id.substring(0, id.lastIndexOf(.));String methodName id.substring(id.lastIndexOf(.) 1);final Method[] method Class.forName(className).getMethods();for (Method me : method) {if (me.getName().equals(methodName) me.isAnnotationPresent(SwitchDataSource.class)) {return me.getAnnotation(SwitchDataSource.class);}}} catch (Exception ex) {log.error(, ex);}return annotation;}Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}Overridepublic void setProperties(Properties properties) {}
}在mapper上打注解标记 SwitchDataSource(name DataSourceEnum.SLAVE)
Long queryTaskOfShopCount(GeneralFeeDetailDTO generalFeeDetailDTO);至此mysql 动态路由到指定数据库就讲解完成。