Spring+MyBatis实现数据读写分离的实例代码
本文介绍了SpringBoot+MyBatis读写分离,有需要了解Spring+MyBatis读写分离的朋友可参考。希望此文章对各位有所帮助。
其最终实现功能:
- 默认更新操作都使用写数据源
- 读操作都使用slave数据源
- 特殊设置:可以指定要使用的数据源类型及名称(如果有名称,则会根据名称使用相应的数据源)
其实现原理如下:
- 通过SpringAOP对dao层接口进行拦截,并对需要指定数据源的接口在ThradLocal中设置其数据源类型及名称
- 通过MyBatsi的插件,对根据更新或者查询操作在ThreadLocal中设置数据源(dao层没有指定的情况下)
- 继承AbstractRoutingDataSource类。
在此直接写死使用HikariCP作为数据源
其实现步骤如下:
- 定义其数据源配置文件并进行解析为数据源
- 定义AbstractRoutingDataSource类及其它注解
- 定义Aop拦截
- 定义MyBatis插件
- 整合在一起
1.配置及解析类
其配置参数直接使用HikariCP的配置,其具体参数可以参考HikariCP。
在此使用yaml格式,名称为datasource.yaml,内容如下:
dds: write: jdbcUrl:jdbc:mysql://localhost:3306/order password:liu123 username:root maxPoolSize:10 minIdle:3 poolName:master read: -jdbcUrl:jdbc:mysql://localhost:3306/test password:liu123 username:root maxPoolSize:10 minIdle:3 poolName:slave1 -jdbcUrl:jdbc:mysql://localhost:3306/test2 password:liu123 username:root maxPoolSize:10 minIdle:3 poolName:slave2
定义该配置所对应的Bean,名称为DBConfig,内容如下:
@Component @ConfigurationProperties(locations="classpath:datasource.yaml",prefix="dds") publicclassDBConfig{ privateListread; privateHikariConfigwrite; publicList getRead(){ returnread; } publicvoidsetRead(List read){ this.read=read; } publicHikariConfiggetWrite(){ returnwrite; } publicvoidsetWrite(HikariConfigwrite){ this.write=write; } }
把配置转换为DataSource的工具类,名称:DataSourceUtil,内容如下:
importcom.zaxxer.hikari.HikariConfig; importcom.zaxxer.hikari.HikariDataSource; importjavax.sql.DataSource; importjava.util.ArrayList; importjava.util.List; publicclassDataSourceUtil{ publicstaticDataSourcegetDataSource(HikariConfigconfig){ returnnewHikariDataSource(config); } publicstaticListgetDataSource(List configs){ List result=null; if(configs!=null&&configs.size()>0){ result=newArrayList<>(configs.size()); for(HikariConfigconfig:configs){ result.add(getDataSource(config)); } }else{ result=newArrayList<>(0); } returnresult; } }
2.注解及动态数据源
定义注解@DataSource,其用于需要对个别方法指定其要使用的数据源(如某个读操作需要在master上执行,但另一读方法b需要在读数据源的具体一台上面执行)
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public@interfaceDataSource{ /** *类型,代表是使用读还是写 *@return */ DataSourceTypetype()defaultDataSourceType.WRITE; /** *指定要使用的DataSource的名称 *@return */ Stringname()default""; }
定义数据源类型,分为两种:READ,WRITE,内容如下:
publicenumDataSourceType{ READ,WRITE; }
定义保存这此共享信息的类DynamicDataSourceHolder,在其中定义了两个ThreadLocal和一个map,holder用于保存当前线程的数据源类型(读或者写),pool用于保存数据源名称(如果指定),其内容如下:
importjava.util.Map; importjava.util.concurrent.ConcurrentHashMap; publicclassDynamicDataSourceHolder{ privatestaticfinalMapcache=newConcurrentHashMap<>(); privatestaticfinalThreadLocal holder=newThreadLocal<>(); privatestaticfinalThreadLocal pool=newThreadLocal<>(); publicstaticvoidputToCache(Stringkey,DataSourceTypedataSourceType){ cache.put(key,dataSourceType); } publicstaticDataSourceTypegetFromCach(Stringkey){ returncache.get(key); } publicstaticvoidputDataSource(DataSourceTypedataSourceType){ holder.set(dataSourceType); } publicstaticDataSourceTypegetDataSource(){ returnholder.get(); } publicstaticvoidputPoolName(Stringname){ if(name!=null&&name.length()>0){ pool.set(name); } } publicstaticStringgetPoolName(){ returnpool.get(); } publicstaticvoidclearDataSource(){ holder.remove(); pool.remove(); } }
动态数据源类为DynamicDataSoruce,其继承自AbstractRoutingDataSource,可以根据返回的key切换到相应的数据源,其内容如下:
importcom.zaxxer.hikari.HikariDataSource; importorg.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; importjavax.sql.DataSource; importjava.util.HashMap; importjava.util.List; importjava.util.Map; importjava.util.concurrent.ConcurrentHashMap; importjava.util.concurrent.ThreadLocalRandom; publicclassDynamicDataSourceextendsAbstractRoutingDataSource{ privateDataSourcewriteDataSource; privateListreadDataSource; privateintreadDataSourceSize; privateMap dataSourceMapping=newConcurrentHashMap<>(); @Override publicvoidafterPropertiesSet(){ if(this.writeDataSource==null){ thrownewIllegalArgumentException("Property'writeDataSource'isrequired"); } setDefaultTargetDataSource(writeDataSource); Map
3.AOP拦截
如果在相应的dao层做了自定义配置(指定数据源),则在些处理。解析相应方法上的@DataSource注解,如果存在,并把相应的信息保存至上面的DynamicDataSourceHolder中。在此对com.hfjy.service.order.dao包进行做拦截。内容如下:
importcom.hfjy.service.order.anno.DataSource; importcom.hfjy.service.order.wr.DynamicDataSourceHolder; importorg.aspectj.lang.JoinPoint; importorg.aspectj.lang.annotation.After; importorg.aspectj.lang.annotation.Aspect; importorg.aspectj.lang.annotation.Before; importorg.aspectj.lang.annotation.Pointcut; importorg.aspectj.lang.reflect.MethodSignature; importorg.springframework.stereotype.Component; importjava.lang.reflect.Method; /** *使用AOP拦截,对需要特殊方法可以指定要使用的数据源名称(对应为连接池名称) */ @Aspect @Component publicclassDynamicDataSourceAspect{ @Pointcut("execution(public*com.hfjy.service.order.dao.*.*(*))") publicvoiddynamic(){} @Before(value="dynamic()") publicvoidbeforeOpt(JoinPointpoint){ Objecttarget=point.getTarget(); StringmethodName=point.getSignature().getName(); Class>[]clazz=target.getClass().getInterfaces(); Class>[]parameterType=((MethodSignature)point.getSignature()).getMethod().getParameterTypes(); try{ Methodmethod=clazz[0].getMethod(methodName,parameterType); if(method!=null&&method.isAnnotationPresent(DataSource.class)){ DataSourcedatasource=method.getAnnotation(DataSource.class); DynamicDataSourceHolder.putDataSource(datasource.type()); StringpoolName=datasource.name(); DynamicDataSourceHolder.putPoolName(poolName); DynamicDataSourceHolder.putToCache(clazz[0].getName()+"."+methodName,datasource.type()); } }catch(Exceptione){ e.printStackTrace(); } } @After(value="dynamic()") publicvoidafterOpt(JoinPointpoint){ DynamicDataSourceHolder.clearDataSource(); } }
4.MyBatis插件
如果在dao层没有指定相应的要使用的数据源,则在此进行拦截,根据是更新还是查询设置数据源的类型,内容如下:
importorg.apache.ibatis.executor.Executor; importorg.apache.ibatis.mapping.MappedStatement; importorg.apache.ibatis.mapping.SqlCommandType; importorg.apache.ibatis.plugin.*; importorg.apache.ibatis.session.ResultHandler; importorg.apache.ibatis.session.RowBounds; importjava.util.Properties; @Intercepts({ @Signature(type=Executor.class,method="update",args={MappedStatement.class,Object.class}), @Signature(type=Executor.class,method="query",args={MappedStatement.class,Object.class, RowBounds.class,ResultHandler.class}) }) publicclassDynamicDataSourcePluginimplementsInterceptor{ @Override publicObjectintercept(Invocationinvocation)throwsThrowable{ MappedStatementms=(MappedStatement)invocation.getArgs()[0]; DataSourceTypedataSourceType=null; if((dataSourceType=DynamicDataSourceHolder.getFromCach(ms.getId()))==null){ if(ms.getSqlCommandType().equals(SqlCommandType.SELECT)){ dataSourceType=DataSourceType.READ; }else{ dataSourceType=DataSourceType.WRITE; } DynamicDataSourceHolder.putToCache(ms.getId(),dataSourceType); } DynamicDataSourceHolder.putDataSource(dataSourceType); returninvocation.proceed(); } @Override publicObjectplugin(Objecttarget){ if(targetinstanceofExecutor){ returnPlugin.wrap(target,this); }else{ returntarget; } } @Override publicvoidsetProperties(Propertiesproperties){ } }
5.整合
在里面定义MyBatis要使用的内容及DataSource,内容如下:
importcom.hfjy.service.order.wr.DBConfig; importcom.hfjy.service.order.wr.DataSourceUtil; importcom.hfjy.service.order.wr.DynamicDataSource; importorg.apache.ibatis.session.SqlSessionFactory; importorg.mybatis.spring.SqlSessionFactoryBean; importorg.mybatis.spring.annotation.MapperScan; importorg.springframework.beans.factory.annotation.Qualifier; importorg.springframework.context.annotation.Bean; importorg.springframework.context.annotation.Configuration; importorg.springframework.core.io.ClassPathResource; importorg.springframework.core.io.support.PathMatchingResourcePatternResolver; importorg.springframework.jdbc.datasource.DataSourceTransactionManager; importjavax.annotation.Resource; importjavax.sql.DataSource; @Configuration @MapperScan(value="com.hfjy.service.order.dao",sqlSessionFactoryRef="sqlSessionFactory") publicclassDataSourceConfig{ @Resource privateDBConfigdbConfig; @Bean(name="dataSource") publicDynamicDataSourcedataSource(){ DynamicDataSourcedataSource=newDynamicDataSource(); dataSource.setWriteDataSource(DataSourceUtil.getDataSource(dbConfig.getWrite())); dataSource.setReadDataSource(DataSourceUtil.getDataSource(dbConfig.getRead())); returndataSource; } @Bean(name="transactionManager") publicDataSourceTransactionManagerdataSourceTransactionManager(@Qualifier("dataSource")DataSourcedataSource){ returnnewDataSourceTransactionManager(dataSource); } @Bean(name="sqlSessionFactory") publicSqlSessionFactorysqlSessionFactory(@Qualifier("dataSource")DataSourcedataSource)throwsException{ SqlSessionFactoryBeansessionFactoryBean=newSqlSessionFactoryBean(); sessionFactoryBean.setConfigLocation(newClassPathResource("mybatis-config.xml")); sessionFactoryBean.setMapperLocations(newPathMatchingResourcePatternResolver() .getResources("classpath*:mapper/*.xml")); sessionFactoryBean.setDataSource(dataSource); returnsessionFactoryBean.getObject(); } }
如果不清楚,可以查看github上源码orderdemo
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。