本文結合實際項目開發經驗(Spring+Mybatis)提出一種實用的數據讀寫分離方案。
主要思路
1.xml配置文件針對讀/寫分別配置不同的Bean,主要包括DataSource(數據源)、SqlSessionFactoryBean以及MapperScannerConfigurer(mapper接口掃描,指定bean生成策略);
2.根據讀寫Bean的生成策略,使用Mapper接口時,通過註解使用不同的bean。
具體實現
1.配置讀寫數據源,由於開發中採用Druid作爲數據庫連接池,因此本文以Druid爲例配置DataSource
主庫配置
<!-- 配置主庫數據源 -->
<bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${masterdb.url}"/>
<property name="username" value="${masterdb.user}"/>
<property name="password" value="${masterdb.password}"/>
<property name="initialSize" value="${initialSize}"/>
<property name="minIdle" value="${minIdle}"/>
<property name="maxActive" value="${maxActive}"/>
<property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}"/>
<property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}"/>
<property name="validationQuery" value="SELECT 'x'"/>
<property name="testWhileIdle" value="${testWhileIdle}"/>
<property name="testOnBorrow" value="${testOnBorrow}"/>
<property name="testOnReturn" value="${testOnReturn}"/>
<property name="removeAbandoned" value="${removeAbandoned}"/>
<property name="removeAbandonedTimeout" value="${removeAbandonedTimeout}"/>
<property name="logAbandoned" value="${logAbandoned}"/>
<property name="filters" value="stat"/>
<property name="connectionProperties" value="druid.stat.slowSqlMillis=${slowSqlMillis}"/>
</bean>
<!-- 配置主庫SqlSessionFactory-->
<bean id="masterSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 使用定義的主庫DataSource -->
<property name="dataSource" ref="masterDataSource" />
<!-- 定義mapper xml文件路徑-->
<property name="mapperLocations">
<list>
<value>classpath*:mapper/*.xml</value>
</list>
</property>
</bean>
<!--指定mapper接口掃描-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定mapper java接口所在的包路徑-->
<property name="basePackage" value="com.dao.mapper" />
<property name="sqlSessionFactoryBeanName" value="masterSqlSessionFactory" />
<!-- 定義用於連接主庫的 mapper接口bean生成策略-->
<property name="nameGenerator" >
<!-- 自定義實現了BeanNameGenerator接口的實現類,bean名稱添加Master後綴 -->
<bean class="com.utils.MapperBeanNameGenerator">
<property name="postfix" value="Master"/>
</bean>
</property>
</bean>
從庫配置,與主庫配置基本一致,slaveDataSource需要將defaultReadOnly設置爲true, 具體如下:
<bean id="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${slavedb.url}"/>
<property name="username" value="${slavedb.user}"/>
<property name="password" value="${slavedb.password}"/>
<property name="initialSize" value="${initialSize}"/>
<property name="minIdle" value="${minIdle}"/>
<property name="maxActive" value="${maxActive}"/>
<property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}"/>
<property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}"/>
<property name="validationQuery" value="SELECT 'x'"/>
<property name="testWhileIdle" value="${testWhileIdle}"/>
<property name="testOnBorrow" value="${testOnBorrow}"/>
<property name="testOnReturn" value="${testOnReturn}"/>
<property name="defaultReadOnly" value="true"/> <!-- 設置爲只讀 -->
<property name="removeAbandoned" value="${removeAbandoned}"/>
<property name="removeAbandonedTimeout" value="${removeAbandonedTimeout}"/>
<property name="logAbandoned" value="${logAbandoned}"/>
<property name="filters" value="stat"/>
<property name="connectionProperties" value="druid.stat.slowSqlMillis=${slowSqlMillis}"/>
</bean>
<!-- 配置從庫SqlSessionFactory-->
<bean id="slaveSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 使用定義的從庫DataSource -->
<property name="dataSource" ref="slaveDataSource" />
<!-- 定義mapper xml文件路徑,可與主庫一致-->
<property name="mapperLocations">
<list>
<value>classpath*:mapper/*.xml</value>
</list>
</property>
</bean>
<!--指定mapper接口掃描-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定mapper java接口所在的包路徑,與主庫一致-->
<property name="basePackage" value="com.dao.mapper" />
<property name="sqlSessionFactoryBeanName" value="slaveSqlSessionFactory" />
<!-- 定義用於連接從庫的 mapper接口bean生成策略,bean名稱添加Slave後綴-->
<property name="nameGenerator" >
<bean class="com.utils.MapperBeanNameGenerator">
<property name="postfix" value="Slave"/>
</bean>
</property>
</bean>
MapperBeanNameGenerator源碼
package com.utils;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.util.StringUtils;
public class MapperBeanNameGenerator implements BeanNameGenerator {
private String postfix = "";
public MapperBeanNameGenerator() {
}
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
String beanClassName = definition.getBeanClassName();
String beanName = beanClassName;
int index = beanClassName.lastIndexOf(".");
if(index != -1) {
beanName = beanClassName.substring(index + 1);
}
return StringUtils.isEmpty(this.postfix) ? beanName : beanName + this.postfix;
}
private String uncapitalize(String str) {
int strLen;
return str != null && (strLen = str.length()) != 0?(new StringBuilder(strLen)).append(Character.toLowerCase(str.charAt(0))).append(str.substring(1)).toString():str;
}
public String getPostfix() {
return this.postfix;
}
public void setPostfix(String postfix) {
this.postfix = postfix;
}
}
這樣配置完成後,啓動後bean會掃描com.dao.mapper定義的java接口文件,並分別生成帶master或slave後綴的bean。
2. 業務調用
@Resource(name = "userMapperMaster")
UserMapper userMapperMaster;
@Resource(name = "userMapperSlave")
UserMapper userMapperSlave;
使用userMapperMaster來調用insert、update相關接口,進行數據插入修改等,使用userMapperSlave調用查詢相關接口。總結
本文僅僅結合實際工作中進行讀寫分離的一種實現方案,使用簡單,可供需要讀寫分離場景提供一種參考,更多方案可參考其他文章