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;
publicListgetRead(){
returnread;
}
publicvoidsetRead(Listread){
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(Listconfigs){
Listresult=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<>();
privatestaticfinalThreadLocalholder=newThreadLocal<>();
privatestaticfinalThreadLocalpool=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;
privateMapdataSourceMapping=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
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。