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>