9月份參加軟件架構師大會,京東老師提到了他們解決數據庫水平切分用的mybatis攔截器來實現,目前所做的項目用的是mybatis,而恰好也需要這個功能,研究了下基本實現了攔截器根據配置自動切分數據表來進行訪問。新老代碼的改造很簡單,加幾個配置即可。
一、具體使用配置
1.1、攔截器配置
<plugins>
<plugin interceptor="com.wagcy.plugin.mybatis.TableSegInterceptor"></plugin>
</plugins>
如果是與spring集成,則配置如下 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="plugins">
<array>
<bean id="tableSegInterceptor" class="com.vrv.im.plugin.mybatis.TableSegInterceptor"/>
</array>
</property>
</bean>
1.2、切分配置
@TableSeg(tableName="Blog",shardType="%5",shardBy="id")
public interface BlogMapper {
Blog selectBlog(int id);
Blog selectBlogByObj(Blog blog);
Blog selectBlogByMap(Map map);
}
tableName分表表名 ;shardType切分類型,如%5:取模,表示取5餘數;shardType:切分字段。這裏只是做了項目中用到最多的切分方式-取模,可以根據需要擴展。二、實現代碼
2.1、攔截器實現
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })
public class TableSegInterceptor implements Interceptor {
private static final String tag = TableSegInterceptor.class.getName();
private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation
.getTarget();
MetaObject metaStatementHandler = MetaObject.forObject(
statementHandler, DEFAULT_OBJECT_FACTORY,
DEFAULT_OBJECT_WRAPPER_FACTORY);
String originalSql = (String) metaStatementHandler
.getValue("delegate.boundSql.sql");
BoundSql boundSql = (BoundSql) metaStatementHandler
.getValue("delegate.boundSql");
//Configuration configuration = (Configuration) metaStatementHandler
//.getValue("delegate.configuration");
Object parameterObject = metaStatementHandler
.getValue("delegate.boundSql.parameterObject");
if (originalSql!=null&&!originalSql.equals("")) {
MappedStatement mappedStatement = (MappedStatement) metaStatementHandler
.getValue("delegate.mappedStatement");
String id = mappedStatement.getId();
String className = id.substring(0, id.lastIndexOf("."));
Class<?> classObj = Class.forName(className);
//根據配置自動生成分表SQL
TableSeg tableSeg = classObj.getAnnotation(TableSeg.class);
if(tableSeg!=null){
AnalyzeActualSql as = new AnalyzeActualSqlImpl(mappedStatement, parameterObject, boundSql);
String newSql=as.getActualSql(originalSql, tableSeg);
if(newSql!=null){
LogUtil.d(tag,"分表後SQL =====>"+ newSql);
metaStatementHandler.setValue("delegate.boundSql.sql", newSql);
}
}
}
// 傳遞給下一個攔截器處理
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
// 當目標類是StatementHandler類型時,才包裝目標類,否者直接返回目標本身,減少目標被代理的
// 次數
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
@Override
public void setProperties(Properties properties) {
// TODO Auto-generated method stub
}
2.2、切分策略解析
讀取切分配置,可以根據自己的需要,擴展實現不同的切分策略。主要邏輯就是讀取切分字段值,然後根據切分策略,得出切分後表的擴展名。2.3、切分字段值獲取
/**
* 獲取字段值
*
* @param propertyName
* @param isMutiPara
* @return
*/
public Object getFieldValue(String propertyName,boolean isMutiPara) {
MetaObject metaObject = parameterObject == null ? null : configuration
.newMetaObject(parameterObject);
Object value;
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject
.getClass())) {
if(isMutiPara)//多個參數,這情況就不應該匹配了
return null;
value = parameterObject;
} else {
value = metaObject == null ? null : metaObject
.getValue(propertyName);
}
return value;
}
2.4、切分配置
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface TableSeg {
/**
* 表名
* @return
*/
public String tableName();
/**
* 分表方式,取模,如%5:表示取5餘數,
* 如果不設置,直接根據shardBy值分表
* @return
*/
public String shardType();
/**
* 根據什麼字段分表
* 多個字段用數學表達表示,如a+b a-b
* @return
*/
public String shardBy();
}