本文主要是介绍Shardbatis开源框架源码的修改实践经验分享,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
Shardbatis开源框架源码按自身业务的改进
摘要
在研发过程中,我们遇到了单表数据量瓶颈问题,同时又不能增加数据库的费用,最后选择了分表技术来解决性能问题。在分表技术的调用过程中,我们有2种技术实现方案。第一种基于mybatis的plugin 插件自研发,一种是采用开源的shardbatis框架。在对比研究分析过程中,发现shardbatis的框架设计理念扩展性良好,对于团队开发有很好规范作用,同时采用配置设计理念,修改配置文件就能满足业务应用场景的需求,最后选择了shardbatis框架,在实际应用过程中发现shardbatis采用sqlparse框架解析SQL,对编写的MySQL的查询语句进行了修正,查询SQL分页需要特殊处理,同时parselist不能到类级别拦截,最后我们下载源码进行了改进,替换了sqlparse解析框架,覆盖了isParse方法,达到满足自己的一套业务需求的框架。
Shardbatis的设计原理
1. 容器启动加载mybatis-config.xml配置文件
2. 解析mybatis配置文件的同时加载mybatis拦截器接口的实现类,通过拦截器接口的setProperties 方法加载shard_config.xml参数,注册分表策略
3. Intercept方法拦截,轮询所有的mapperid与分表策略中配置的mapperid比较,是否相等,相等表示需要进行分表,如果需要分表,就走分表代码执行逻辑,不分表直接调用invocation.proceed() 返回
4. 分表逻辑,读取StatementHandler 的sql 进行表替换,表的替换按照分表对应的具体策略进行替换,替换完成,重新绑定SQL,调用invocation.proceed() 返回
5. 业务分表策略必须实现此接口,才能被拦截器解析.
mybatis-config.xml
<?xml version="1.0"encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="lazyLoadingEnabled"value="false" />
<!-- 全局延迟加载 ,在启用延迟加载的同时,需要禁用"aggressiveLazyLoading"项 -->
<setting name="aggressiveLazyLoading"value="false" />
<!-- 这个设置项在用户手册当中的定义当启用时,有延迟加载属性的对象在被调用时将会完全加载任意属性。否则,每种属性将会按需要加载。 -->
<setting name="lazyLoadTriggerMethods"value="clone" />
<!-- <setting name="defaultExecutorType"value="BATCH" /> -->
</settings>
<plugins>
<plugin interceptor="com.google.code.shardbatis.plugin.ShardPlugin">
<propertyname="shardingConfig" value="shard_config.xml"/>
</plugin>
</plugins>
<plugins>
</plugins>
shard_config.xml
<?xml version="1.0"encoding="UTF-8"?>
<!DOCTYPE shardingConfigPUBLIC "-//shardbatis.googlecode.com//DTDShardbatis 2.0//EN"
"http://shardbatis.googlecode.com/dtd/shardbatis-config.dtd">
<shardingConfig>
<!--parseList可选配置如果配置了parseList,只有在parseList范围内的sql才会被解析和修改,配置到dao层 -->
<parseList>
<value>com.xxx.member.dao.xxxEdt.insert</value>
</parseList>
<!-- 配置分表策略 -->
<strategy tableName="t_crm_member"strategyClass="com.xxx.member.dao.UserIdStrategy" />
</shardingConfig>
代码分析如下:
com.google.code.shardbatis.plugin.ShardPlugin核心类
com.google.code.shardbatis.strategy.ShardStrategy 核心接口
@Intercepts( { @Signature(type = StatementHandler.class, method = "prepare",args = { Connection.class })})
publicclass ShardPlugin implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable{
StatementHandler statementHandler =(StatementHandler) invocation.getTarget();
MappedStatement mappedStatement = null;
if (statementHandlerinstanceofRoutingStatementHandler) {
StatementHandler delegate = (StatementHandler)ReflectionUtils
.getFieldValue(statementHandler, "delegate");
mappedStatement = (MappedStatement)ReflectionUtils.getFieldValue(delegate, "mappedStatement");
} else {
mappedStatement = (MappedStatement)ReflectionUtils.getFieldValue(statementHandler, "mappedStatement");
}
String mapperId = mappedStatement.getId();
if (isShouldParse(mapperId)) {
String sql = statementHandler.getBoundSql().getSql();
if (log.isDebugEnabled()) {
log.debug("Original Sql ["+ mapperId+ "]:"+ sql);
}
Object params = statementHandler.getBoundSql().getParameterObject();
SqlConverterFactory cf = SqlConverterFactory.getInstance();
sql= cf.convert(sql,params, mapperId);
if (log.isDebugEnabled()) {
log.debug("Converted Sql ["+ mapperId+ "]:"+ sql);
}
ReflectionUtils.setFieldValue(statementHandler
.getBoundSql(), "sql", sql);
}
returninvocation.proceed();
}
publicObject plugin(Object target){
}
public void setProperties(Properties properties){
}
}
publicinterface ShardStrategy{
/**
* 得到实际表名
* @param baseTableName 逻辑表名,一般是没有前缀或者是后缀的表名
* @param params mybatis执行某个statement时使用的参数
* @param mapperId mybatis配置的statement id
* @return
*/
String getTargetTableName(String baseTableName,Object params,String mapperId);
}
Shardbatis优化改进
1. SqlConverterFactory.convert 方法
源码:
SqlConverterFactory cf =SqlConverterFactory.getInstance();
sql = cf.convert(sql,params, mapperId);
重写convert方法:
sql = convert(sql, params, mapperId);
protected String convert(String sql,Object params, String mapperId) {
ShardConfigHolder configFactory =ShardConfigHolder.getInstance();
Map<String, ShardStrategy> strategyRegister=configFactory.getStrategyRegister();
Iterator<String> iterators=strategyRegister.keySet().iterator();
while(iterators.hasNext()){
String tableName = iterators.next().toUpperCase();
//获取分表策略并转换sql
ShardStrategy strategy =configFactory.getStrategy(tableName);
String shardTable =strategy.getTargetTableName(tableName, params, mapperId);
sql =sql.replaceAll(jointRegex(tableName), jointShard(shardTable));
}
returnsql ;
}
/**
* 生成匹配正则
*
* @param tabelName 表名
* @return
*/
private String jointRegex(String tabelName) {
returnnew StringBuffer(REGEX_PREFIX).append(tabelName).append(REGEX_SUFIX).toString();
}
/**
* 拼接分表名
*
* @param shardTable 分表名
* @return
*/
private String jointShard(String shardTable) {
returnnew StringBuffer(SPACE).append(shardTable).append(SPACE).toString();
}
2. ShardConfigHolder. isParseId方法
shardbatis框架配置必须要到方法,不能到类级别,而我们业务中都是类级别的,对源码进行了修改,满足自身的要求
源码:
<?xml version="1.0"encoding="UTF-8"?>
<!DOCTYPE shardingConfigPUBLIC "-//shardbatis.googlecode.com//DTDShardbatis 2.0//EN"
"http://shardbatis.googlecode.com/dtd/shardbatis-config.dtd">
<shardingConfig>
<!--parseList可选配置如果配置了parseList,只有在parseList范围内的sql才会被解析和修改,配置到dao层 -->
<parseList>
<value>com.xxx.member.dao.xxxEdt.insert</value>
</parseList>
<!-- 配置分表策略 -->
<strategy tableName="t_crm_member"strategyClass="com.xxx.member.dao.UserIdStrategy" />
</shardingConfig>
修改:
<?xml version="1.0"encoding="UTF-8"?>
<!DOCTYPE shardingConfigPUBLIC "-//shardbatis.googlecode.com//DTDShardbatis 2.0//EN"
"http://shardbatis.googlecode.com/dtd/shardbatis-config.dtd">
<shardingConfig>
<!--parseList可选配置如果配置了parseList,只有在parseList范围内的sql才会被解析和修改,配置到dao层 -->
<parseList>
<value> com.xxx.member.dao.xxxEdt </value>
</parseList>
<!-- 配置分表策略 -->
<strategy tableName="t_crm_member"strategyClass="com.xxx.member.dao.UserIdStrategy" />
</shardingConfig>
publicclass ShardConfigHolder {
publicboolean isParseId(String id) {
id=id.substring(0,id.lastIndexOf("."));
returnparseSet != null && parseSet.contains(id);
}
}
最后,由于笔者水平有限,文章中如有不足之处,敬请读者斧正,文明交流,沟通分享。
这篇关于Shardbatis开源框架源码的修改实践经验分享的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!