当前位置: 首页 > 科技观察

SpringBoot读写分离组件开发详解

时间:2023-03-21 13:03:50 科技观察

环境:springboot2.2.6RELEASE实现目标:一写多读,多读可以任意配置,默认是从写库操作,只有满足的方法条件(指定目标方法或标有指定注解的方法将从读库中操作)。独立打包成jar包放在本地仓库。实现原理:通过aop。1.pom.xml配置文件org.springframework.bootspring-boot-starterorg.springframework.bootspring-boot-starter-data-jpaorg.springframework.bootspring-boot-configuration-processortrue2.application.yml配置文件pack:datasource:pointcut:execution(public*net.greatsoft.service.base.*.*(..))||execution(public*net.greatsoft.service.xxx.*.*(..))master:driverClassName:oracle.jdbc.driver.OracleDriverjdbcUrl:jdbc:oracle:thin:@10.100.102.113:1521/orclusername:testpassword:testminimumIdle:10maximumPoolSize:200autoCommit:trueidleTimeout:30000poolName:MbookHikariCPmaxLifetime:1800000connectionTimeout:30000connectionTestQuery:SELECT1FROMDUALslaves:-driverClassName:oracle.jdbc.driver.OracleDriverjdbcUrl:jdbc:oracle:thin:@10.100.102.113:1521/orclusername:dcpassword:dcminimumIdle:10maximumPoolSize:200autoCommit:trueidleTimeout:30000poolName:MbookHikariCPmaxLifetime:1800000connectionTimeout:30000connectionTestQuery:SELECT1FROMDUAL-driverClassName:oracle.jdbc.driver.OracleDriverjdbcUrl:jdbc:oracle:thin:@10.100.102.113:1521/orclusername:empipassword:empiminimumIdle:10maximumPoolSize:200autoCommit:trueidleTimeout:30000poolName:MbookHikariCPmaxLifetime:1800000connectionTimeout:30000connectionTestQuery:SELECT1FROMDUALpointcut:定义切点,那些方法是需要拦截(从读库操作)master:写库配置。slaves:读库配置(Listcollection)。3.属性配置类@Component@ConfigurationProperties(prefix="pack.datasource")publicclassRWDataSourceProperties{privateStringpointcut;privateHikariConfigmaster;privateListslaves=newArrayList<>();}4.读取配置类publicclassRWConfig{privatestaticLoggerlogger=LoggerFactory.getLogger(RWConfig.class);@BeanpublicHikariDataSourcemasterDataSource(RWDataSourcePropertiesrwDataSourceProperties){returnnewHikariDataSource(rwDataSourceProperties.getMaster());}@BeanpublicListslaveDataSources(RWDataSourcePropertiesrwDataSourceProperties){Listlists=newArrayList<>();for(HikariConfigconfig:rwDataSourceProperties)getSlaves()){lists.add(newHikariDataSource(config));}returnlists;}@Bean@Primary@DependsOn({"masterDataSource","slaveDataSources"})publicAbstractRoutingDataSourcoutingDataSource(@Qualifier("masterDataSource")DataSourcemasterDataSource,@Qualifier(“奴隶DataSources")ListslaveDataSources){BaseRoutingDataSourceds=newBaseRoutingDataSource();MaptargetDataSources=newHashMap<>(2);targetDataSources.put("master",masterDataSource);for(inti=0;icontext=newThreadLocal(){@OverrideprotectedIntegerinitialValue(){return0;}};@Return0;};@Return0;}){Integertype=context.get();returntype==null||type==0?"master":"slave-"+slaveLoad.load();}publicvoidset(Integertype){context.set(type);}}通过aop动态设置context的内容值,0表示从写库操作,其他在读库操作。BaseSlaveLoad类是从读库中选出来的。算法类,默认实现使用轮询算法。publicinterfaceBaseSlaveLoad{intload();}publicabstractclassAbstractSlaveLoadimplementsBaseSlaveLoad{@ResourceprotectedListslaveDataSources;}这里定义了一个抽象类注入到库列表中,所有的实现类都可以继承这个类。publicclassPollingLoadextendsAbstractSlaveLoad{privateintindex=0;privateintsize=1;@PostConstructpublicvoidinit(){size=slaveDataSources.size();}@Overridepublicintload(){intn=index;synchronized(this){index=(++index)%size;}returnn;}}配置成Bean@Bean@ConditionalOnMissingBeanpublicBaseSlaveLoadslaveLoad(){returnnewPollingLoad();}@BeanpublicDataSourceHolderdataSourceHolder(){returnnewDataSourceHolder();}6.数据源AOPpublicclassDataSourceAspectimplementsMethodInterceptor{privateDataSourceHolderholder.publicDataSourceholderAspect}(MethodInvocationinvocation)throwsThrowable{Methodmethod=invocation.getMethod();StringmethodName=method.getName();SlaveDBslaveDB=method.getAnnotation(SlaveDB.class);如果(slaveDB==null){slaveDB=method.getDeclaringClass().getAnnotation(SlaveDB.class);}if(methodName.startsWith("find")||methodName.startsWith("get")||methodName.startsWith("query")||methodName.startsWith("select")||methodName.startsWith("list")||slaveDB!=null){holder.set(1);}else{holder.set(0);}returninvocation.proceed();}}应该是动态配置的,所以用springaop来配置@BeanpublicAspectJExpressionPointcutAdvisorlogAdvisor(RWDataSourcePropertiesprops,DataSourceHolderholder){AspectJExpressionPointcutAdvisor=newAspectJExpressionPointcutAdvisor();logger.info("Executionexpression:{}",props.getPointcut());advisor。setExpression(props.getPointcut());advisor.setAdvice(newDataSourceAspect(holder));returnadvisor;}7.开启函数publicclassRWImportSelectorimplementsImportSelector{@OverridepublicString[]selectImports(AnnotationMetadataimportingClassMetaW){returnnewStringf[ig]getName()};}}这里的RWConfig就是我们上面的配置类@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documented@Import({RWImportSelector.class})public@interfaceEnableRW{}@Documented@Retention(RUNTIME)@Target({TYPE,方法})public@interfaceSlaveDB{}有@SlaveDB的注解方法和类都会从读库操作到这个读写分离组件的开发。8.打包安装到本地仓库mvninstall-Dmaven.test.skip=true9.新建base-web项目引入依赖com.packxg-component-rw1.0.0在启动类中添加注解,开启读写分离@SpringBootApplication@EnableRWpublicclassBaseWebApplication{publicstaticvoidmain(String[]args){SpringApplication.run(BaseWebApplication.class,args);}}测试:第一个查询:第二个查询:为了区分两个从库,这里设置不同的数据是写库完成的!!!