Spring多数据源配置系列(三)——读写分离
- 2019 年 10 月 30 日
- 筆記
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/luo4105/article/details/77773027
资源
AbstractRoutingDataSource亦可用来做读写分离。读写分离实际上也算多数据源,有读数据源、写数据源,通过一定规则使写用写数据源,读用读数据源。
读写分离介绍
读写分离常用的策略有两种,一种是使用MySql中间件,如mysql-proxy之类。这种对代码没有侵入、没有影响,运维就能完成、维护;第二种是在应用层解决,这里介绍的就是应用层使用spring的AbstractRoutingDataSource来完成项目的读写分离。
现在比较常使用的是使用MySql中间件,这样职责分开,也便于维护。但在一些创业公司人手不足,分工不能明确,这样就可以选择第二种,在应用层解决。
原理
我们通过service方法名头来判断是读还是写。比如”get,select”就是读操作,”update,delete,save,insert”就是写操作。
将读写操作关键字和方法名头的对应关系保存起来,如:{“read”,[“get”,” select”]}。这是一个配置项,可以用static Map<String,String[]>存放。
还需获得方法名,这个可以用切面获得,存放到ThreadLocal。
实现
实现步骤为
1. 配置读写数据源
2. 实现AbstractRoutingDataSource类
3. 注册实现类
4. 编写AOP
配置数据源
<bean id="readDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/aicms" /> <property name="username" value="root" /> <property name="password" value="123456" /> </bean> <bean id="writeDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/aicms" /> <property name="username" value="root" /> <property name="password" value="123456" /> </bean>
实现AbstractRoutingDataSource类
public class ReadWriteDynamicDataSource extends AbstractRoutingDataSource { public static Map<String, String[]> readWriteKey; public static ThreadLocal<String> currentServiceMethod = new ThreadLocal<>(); public void setReadWriteKey(Map<String, String> map) { ReadWriteDynamicDataSource.readWriteKey = new HashMap<>(); for(Map.Entry<String, String> item : map.entrySet()) { ReadWriteDynamicDataSource.readWriteKey.put(item.getKey(), item.getValue().split(",")); } } @Override protected Object determineCurrentLookupKey() { String serviceMethod = currentServiceMethod.get(); l:for(Map.Entry<String, String[]> item : readWriteKey.entrySet()){ for (String val : item.getValue()) { if(StringUtils.startsWithIgnoreCase(serviceMethod, val)) { System.out.println("datasource:" + item.getKey()); return item.getKey(); } } } return null; } }
注册实现类
<bean id="dynamicDataSource" class="com.lc.rwrout.ReadWriteDynamicDataSource"> <property name="targetDataSources"> <map> <entry key="read" value-ref="readDataSource"/> <entry key="write" value-ref="writeDataSource"/> </map> </property> <property name="readWriteKey"> <map> <entry key="read" value="get,select" /> <entry key="write" value="update,del,save,add,insert" /> </map> </property> <property name="defaultTargetDataSource" ref="writeDataSource"> </property> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dynamicDataSource" /> <property name="mapperLocations" value="mapper/*.xml" /> <property name="typeAliasesPackage" value="com.lc.model" /> </bean>
编写AOP
public class ReadWriteAspect { public Object around(ProceedingJoinPoint jp) throws Throwable { ReadWriteDynamicDataSource.currentServiceMethod.set(jp.getSignature().getName()); Object rvt = null; //执行原方法,也可以不执行 rvt = jp.proceed(jp.getArgs()); ReadWriteDynamicDataSource.currentServiceMethod.remove(); return rvt; } }
<bean id="dataSourceAspect" class="com.lc.rwrout.ReadWriteAspect"></bean> <aop:config> <aop:aspect id="aspect" ref="dataSourceAspect" order="0"> <aop:pointcut id="servicePointCut" expression="execution(* com.lc.service.*Service.*(..))" /> <aop:around method="around" pointcut-ref="servicePointCut" /> </aop:aspect> </aop:config>