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

5 篇文章 0 订阅
1 篇文章 0 订阅

Mybatis Plugin插件种类

mybatis支持对于ExecutorStatementHandlerPameterHandlerResultSetHandler做拦截。要想通过拦截器做分表路由可以在ExecutorStatementHandler两个阶段进行拦截。本次的路由实现是在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文件的解析&MybatisPlugin代码逻辑

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.0 sessionFactoryBuilder sessionFactoryBuilder xmlConfigBuilder xmlConfigBuilder Configuration Configuration InterceptorChain InterceptorChain parser.parse() builder.build()调用 XmlConfigBuilder.parser() configuration.addInterceptor() XMLConfiguretion解析 配置文件将Plugin 实例化后加入到全 文配置Configure中 chain.addInterceptor() 最终调用InterceptorChain的 addInterceptor()方 法加入掉调用链上。
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值