当前位置: 首页 > news >正文

巩义网站推广咸阳网站建设公司

巩义网站推广,咸阳网站建设公司,评级网站怎么做,wordpress同步twitter使用AbstractRoutingDataSource实现多数据源 与 获取mapper上注解 背景 随着业务发展速度越来越快,数据的增长也呈现倍数级别增长,数据库的压力,对于查询和写入等所有操作,都依赖于主库,其实有一些对于时效性要求不高…

使用AbstractRoutingDataSource实现多数据源 与 获取mapper上注解

背景

        随着业务发展速度越来越快,数据的增长也呈现倍数级别增长,数据库的压力,对于查询和写入等所有操作,都依赖于主库,其实有一些对于时效性要求不高的场景,无需使用主库查询,可以使用从库来分摊主库的压力,提升数据库集群整体的吞吐量

处理思路

其实处理思路共有两种

1. 我们创建两个数据源,不同的数据源扫描不同的包.
        也就是查询从库或者查询主库,是通过不同的mapper文件物理隔离的,优势就是开发简单,快速搭建,不用担心侵入原有逻辑。缺点也是显而易见的,如果一个sql在某些场景下需要主库查询,另外场景需要从库查询,那么我们要写两套sql,如果有新增字段等ddl操作时,我们要改两遍sql,由于比较简单,本文不做描述。

2. 使用动态数据源配置
        在多个数据源之上,抽象出来一个虚拟的动态数据源,等到执行sql的前一步,我们才会选择哪个数据源执行,优点是能够更精细化的管控sql执行,缺点的话就是开发稍微复杂些

动态数据源的执行逻辑

我们需要先了解下spring的AbstractRoutingDataSource ,spring提供了一套数据库路由方案,我们重写determineCurrentLookupKey 方法,将想要的数据库返回即可。

以spring boot 为例

一、 mysql的基本配置

  1. 首先配置主库和从库的数据源,这个是前提

    @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;
    }
    
  2. 配置动态数据源,将多数据源注入进动态数据源,并设置默认的数据源

    @Bean(name = "dynamicDataSource")
    public DynamicDataSource dynamicDataSource(@Qualifier("mysqlDataSource") DataSource mysqlDataSource,@Qualifier("mysqlSlaveDataSource") DataSource mysqlSlaveDataSource) {Map<Object, Object> targetDataSources = Maps.newHashMap();targetDataSources.put(DataSourceEnum.MASTER, mysqlDataSource);targetDataSources.put(DataSourceEnum.SLAVE, mysqlSlaveDataSource);return new DynamicDataSource(mysqlDataSource, targetDataSources);
    }
    
  3. 将动态数据源设置到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();
    }
    

二、 新增的配置

  1. 新增加库的枚举定义
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;}
}
  1. 动态数据源的定义,需要继承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, Map<Object, 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();}}
  1. 新增注解

    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;
    }
    
  2. 拦截器改造需要拦截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) {}
    }
    
  3. 在mapper上打注解标记

    @SwitchDataSource(name = DataSourceEnum.SLAVE)
    Long queryTaskOfShopCount(GeneralFeeDetailDTO generalFeeDetailDTO);
    

    至此,mysql 动态路由到指定数据库就讲解完成。

http://www.zhongyajixie.com/news/47481.html

相关文章:

  • 陕西建设厅八大员官方网站百度推广一级代理商名单
  • wamp配置多个网站百度推广费用可以退吗
  • interidea 做网站成功营销十大经典案例
  • 怎么做网站赚钱公司网站建设步骤
  • 网站模板怎么做视频教程泰州seo网站推广
  • 怎么做电影网站的宁波建站模板系统
  • mac怎样买wordpressseo网站关键词排名快速
  • 网站建设公司招人微信管理软件
  • 机械厂网站建设中山seo推广优化
  • 网站外链建设策略中国国家人才培训网官网
  • 网站建设的规划方案2022网站快速收录技术
  • 怎么做购物网站关键词优化公司
  • 网站开发项目周期的培训心得体会1500字
  • 雄安企业网站建设新东方烹饪学校学费一年多少钱
  • 电子网站建设设计长沙网络推广
  • 企业营销网站案例小程序开发需要哪些技术
  • css优秀网站门户网站有哪些
  • 动态网页制作技术哪些百度seo是啥
  • 网站seo方案策划书百度外链查询工具
  • 做花生的网站产品推广软件有哪些
  • 联盟网站制作游戏推广平台哪个好
  • 公司门户网站建设方案百度网页网址
  • 宝安做网站哪家好b站推广网站mmm
  • 阳江做网站的公司网络营销推广
  • 用jsp做的网站有哪些今日新闻内容
  • 建站产品抖音视频seo霸屏
  • 做像百姓网这样网站多少钱尚硅谷培训机构官网
  • 网站建设中存在的问题怎么学seo基础
  • 微网站 布局电商网站建设开发
  • 合肥做机床的公司网站制作网页