您的位置:首页 > 编程语言 > Java开发

Java Mybatis Plugin插件实现分表路由规则

2015-08-03 18:37 453 查看

Mybatis Plugin插件种类

mybatis支持对于
Executor
StatementHandler
PameterHandler
ResultSetHandler
做拦截。要想通过拦截器做分表路由可以在
Executor
StatementHandler
两个阶段进行拦截。本次的路由实现是在
StatementHandler
拦截Sql在通过Rule修改Sql的表名,这样系统原有的Sql不用修改表名会自动替换成路由计算出的表名。

定义mybatis-config.xml配置文件

<plugins>
<plugin interceptor="com.*.settlement.provider.bill.prepay.dao.shard.BillShardDefault">
<!-- 必须以xxStrategy结尾 -->
<property name="defaultStrategy" value="com.strategy.DefaultShardStrategy"/>

<!--
prepay_bill_detail_index|defaultStrategy,
prepay_bill_detail_content,
prepay_bill_sheet_fee
-->
<!-- 表名|路由策略 -->
<!-- 策略必须继承 com.strategy.ShardStrategy -->
<!-- 如果不指定Strategy则使用默认的策略 -->
<!-- 使用了路由策略表对应的Dao method 必须包含参数
@Param("_shardParam")@NotNull ShardParam shardParam
参见: void insert(
@Param("detailIndex")PrepayBillDetailIndex billDetail,
@Param("_shardParam")@NotNull ShardParam shardParam);
-->
<property name="tableNames"
value="
prepay_bill_detail_index|defaultStrategy,
prepay_bill_detail_content,
prepay_bill_sheet_fee"
/>

</plugin>
</plugins>


编写自己的拦截器继承拦截器接口类Plugin

ShardStrategy 接口 & 实现类 DefaultShardStrategy

ShardStrategy 接口:
/**
* Shard分表策略
* Created by xueping.you on 15-7-29.
*/
public interface ShardStrategy{

/**
* 获取表名
* @param param
* @return
*/
String getTableName(String tableName ,ShardParam param);

}

DefaultShardStragy 实现:

/**
* Created by xueping.you on 15-7-29.
*/
public class DefaultShardStrategy extends ShardStrategy{

/**
* @param tableName 例如:test
* @param param1 例如:new ShardParam(new Date() , BizSystem.OTR)
* @return 例如:test_201509
*/
@Override
public String getTableName(String tableName , ShardParam param1) {
StringBuilder builder = new StringBuilder(tableName);
builder.append("_");
builder.append(getAssShardParam(param1));
return builder.toString();
}

public static String getAssShardParam(ShardParam param1){
StringBuilder builder = new StringBuilder();
if(param1.getBizSystem().equals(BizSystem.OTR)){
builder.append(BizSystem.OTR.name());
}
builder.append(param1.getDivideDate().getYear() + 1900 + "");
int month = param1.getDivideDate().getMonth() + 1;
if( month <10 ){
builder.append("0"+month);
}else {
builder.append(month);
}
return builder.toString();
}

}


Interceptor实现

可以通过Annotation
@Interceptor
申明拦截器属于四中拦截器里面的哪种,并且可以指定拦截的接口方法以及方法的参数

/**
* Created by xueping.you on 15-7-29.
*/
@Intercepts(
{ @Signature(type = StatementHandler.class,
method = "prepare",
args = { Connection.class })
})
public class BillShardDefault implements Interceptor {
private final static Logger logger = LoggerFactory.getLogger(BillShardDefault.class);

private String tableName;

private List<String> tableNames = Lists.newArrayList();

/**
* 线程安全类,初始化常量,避免重复创建
* **/
private final static ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();

/**
* 线程安全类,初始化常量,避免重复创建
* **/
private final static ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY =
new DefaultObjectWrapperFactory();

private final static String BOUNDSQL_NAME = "delegate.boundSql";

private final static String BOUNDSQL_SQL_NAME = "delegate.boundSql.sql";

private final static String SQL_PARAM_NAME = "delegate.parameterHandler.parameterObject";

private final static ShardStrategy DEFAULTSTRATEGY = new DefaultShardStrategy();

private final static String SHARDPARAM = "_shardParam";

private final static String STRATEGY_SUFFIX = "Strategy";

private Map<String , ShardStrategy> STRATEGY_CONTEXT = Maps.newHashMap();

private Map<String , ShardStrategy> TABLE_ROUTER = Maps.newHashMap();

@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
MetaObject metaObject = MetaObject.forObject(
statementHandler ,
DEFAULT_OBJECT_FACTORY ,
DEFAULT_OBJECT_WRAPPER_FACTORY);

BoundSql boundSql = (BoundSql)metaObject.getValue(BOUNDSQL_NAME);
String executeSql = boundSql.getSql();
MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap)metaObject.getValue(SQL_PARAM_NAME);
ShardParam shardParam = null;
if (paramMap.containsKey(SHARDPARAM)) {
shardParam = (ShardParam)paramMap.get(SHARDPARAM);
}
/** 临时注释掉这段代码 数据迁移完成需要去掉 **/
//checkArgument(shardParam!=null , "_shardParam Param Can't Be Null");
if(shardParam!=null){ /** 临时if条件判断数据迁移完成需要去掉 **/
executeSql = decorateSql(executeSql , shardParam);
metaObject.setValue(BOUNDSQL_SQL_NAME,executeSql);
}
return invocation.proceed();
}

private String decorateSql(String executeSql, ShardParam shardParam) {
for(String tableName : tableNames){
String newTaleName = TABLE_ROUTER.get(tableName).getTableName(tableName , shardParam);
executeSql = executeSql.replaceAll(tableName,newTaleName);
}
return executeSql;
}

@Override
public Object plugin(Object target) {

if (target instanceof StatementHandler) {
return Plugin.wrap(target , this);
}else {
return target;
}
}

/**
* Don't Modify Any Code
* @param properties
*/
@Override
public void setProperties(Properties properties) {
setShardStrategy(properties);
setTableNames(properties);
}

private void setTableNames(Properties properties){
tableName = properties.getProperty("tableNames");
checkArgument(!Strings.isEmpty(tableName) , "参数[tableNames]必填!");
List<String> tempTableRouterStrList = Lists.newArrayList(
Splitter.on(",").trimResults().omitEmptyStrings().split(tableName)
);
for(String tempTableRouterStr : tempTableRouterStrList){
List<String> single = Lists.newArrayList(
Splitter.on("|").omitEmptyStrings().trimResults().split(tempTableRouterStr)
);
checkArgument(!CollectionUtils.isEmpty(single) , "Config is not correct!");
tableNames.add(single.get(0));
if(single.size()==1){
TABLE_ROUTER.put(single.get(0), DEFAULTSTRATEGY);
}else {
TABLE_ROUTER.put(single.get(0), STRATEGY_CONTEXT.get(single.get(1)));
}
}
}

private void setShardStrategy(Properties properties){
try {
for(Map.Entry entry : properties.entrySet()){
String strategyClassNameKey = entry.getKey().toString();
if(strategyClassNameKey.indexOf(STRATEGY_SUFFIX)!=-1){
String strategyClassName = entry.getValue().toString();
Class strategyClass = Class.forName(strategyClassName);

Object o = strategyClass.newInstance();
if(o instanceof ShardStrategy){
STRATEGY_CONTEXT.put(strategyClassNameKey , (ShardStrategy)o);
}else {
throw new IllegalStateException(
"strategyClass must implement interface ShardStrategy<P>"
);
}
}
}
}catch (Exception e){
logger.error("生成ShardStrategy策略失败", e);
throw new RuntimeException(e);
}

}
}


PS说说Mybatis
mybatis-config.xml
文件的解析&Mybatis
Plugin
代码逻辑

Mybatis通过 SqlSessionFactoryBuilder 构造 SqlSessionFactory

在mybatis中存在一个
SqlSessionFactoryBuilder
类用于在实例起来时构造Session工厂实例。涉及到的最终方法:
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {....}
该方法中会调用
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);


return build(parser.parse()); 去解析配置文件,里面会涉及到解析pulgins配置、typeAliases配置、settings配置etc,最终会将配置加载到全文配置Configure中,在Executor 或者 StatementHandler中会使用
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
来执行Plugin。

解析方法链:

Created with Raphaël 2.1.0sessionFactoryBuildersessionFactoryBuilderxmlConfigBuilderxmlConfigBuilderConfigurationConfigurationInterceptorChainInterceptorChainparser.parse()builder.build()调用XmlConfigBuilder.parser()configuration.addInterceptor()XMLConfiguretion解析配置文件将Plugin实例化后加入到全文配置Configure中chain.addInterceptor()最终调用InterceptorChain的addInterceptor()方法加入掉调用链上。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  分表 路由 插件 mybatis