赞
踩
学习一个框架除了搞清楚核心原理外,还有很关键的需要知道如何去扩展它,而对Mybatis来说,它提供了插件的方式帮助我们进行功能扩展,所以学习如何自定义一个插件也是非常有必要的,本文介绍一个基于插件如何实现一个分表的操作,相信从这个示例的学习,读者会有些收获的。本文还介绍了Spring如何与Mybatis整合的原理,帮助读者更多的了解Mybatis。
插件能够改变或者扩展Mybatis的原有的功能,像Mybatis涉及到分页操作,通常就会去引用分页插件来实现分页功能,所以搞懂插件还是非常有必要的。下面通过一个简单的自定义插件来实战一个分片功能,来让读者对插件有更深的了解。
首先,自定义一个分片路由插件,通过@Intercepts定义插件的拦截的目标类型,当前插件主要拦截StatementHandler类型,对query、update、prepare方法进行拦截
@Intercepts( { @Signature(type=StatementHandler.class,method="prepare",args={ Connection.class,Integer.class}), @Signature(type=StatementHandler.class,method="query",args={ Statement.class,ResultHandler.class}), @Signature(type=StatementHandler.class,method="update",args={ Statement.class}) } ) public class MyRoutePlugin implements Interceptor{ @Autowired private RouteConfig routeConfig; @Autowired private DataSource datasouce; }
同时定义一个分片规则配置类,其中routeKey为分片字段,决定当前执行的SQL语句是否需要分片
@ConfigurationProperties(prefix="route")
@Component
@Data
public class RouteConfg {
private boolean enabled=true;
//分片字段
private String routeKey;
//分片表的数量
private int tableCount;
}
接下来,重写intercept方法,定义路由插件的实现逻辑,核心逻辑就是根据StatementHandler一步步通过反射拿到当前执行的SQL语句,然后确认是否开启路由分片,开启之后就会进行后续分片处理
public Object intercept(Invocation invocation) throws Throwable { //先判断拦截的目标类型是否为StatementHandler if(invocation.getTarget() instanceof StatementHandler){ //获得具体statementHandler实现,这里拿到的是RoutingStatementHandler StatementHandler statementHandler=(StatementHandler) invocation.getTarget(); //通过RoutingStatementHandler,反射拿到它内部的属性delegate,这个delegate就是具体的statementHandler实现 Field delegate= getField(statementHandler,"delegate"); //获得statementHandler实现 PreparedStatementHandler preparedStatementHandler= (PreparedStatementHandler) delegate.get(statementHandler); //拿到boundSql BoundSql boundSql= preparedStatementHandler.getBoundSql(); //拿到它内部sql语句 Field sqlF=getField(boundSql, "sql"); String sql=(String) sqlF.get(boundSql); if(routeConfig.isEnabled()){ //处理分片 return handlerRoute(sql,boundSql,preparedStatementHandler,invocation,sqlF); } } return invocation.proceed(); }
//获得实例中指定name的field
public Field getField(Object obj,String fieldName){
Field field=ReflectionUtils.findField(obj.getClass(), fieldName);
//设置访问权限
ReflectionUtils.makeAccessible(field);
return field;
}
接下来,根据不同的SQL语句类型进行不同分片处理,这里只实现了对查询和插入的分片
//处理路由分片
private Object handlerRoute(String sql, BoundSql boundSql,
PreparedStatementHandler preparedStatementHandler,Invocation invocation,Field sqlF) throws InvocationTargetException, IllegalAccessException, IllegalArgumentException, SQLException {
//判断查询类型
if(sql.contains("select")||sql.contains("SELECT")){
return hanlderSelectRoute(sql,boundSql,preparedStatementHandler,invocation,sqlF);
}
if(sql.contains("insert")||sql.contains("INSERT")){
return handlerInsertRoute(sql,boundSql,preparedStatementHandler,invocation,sqlF);
}
return invocation.proceed();
}
查询的分片处理流程: 先根据执行的SQL语句获得旧的表名,接下来判断是否配置了路由分片字段,如果配置的话,那么会通过当前SQL语句的入参信息,确认SQL语句的查询参数中是否有包含分片字段。如果查询参数满足分片,那么就会根据对应的参数值计算它的hash值,然后基于hash值根据分片表数进行求余,确认分片后的表名,最后通过反射将当前执行的BoundSql中SQL语句替换为新的SQL语句,最终查询就会基于新的SQL语句进行查询,实现了分片效果。
//获得查询语句的表名
private String getSelectName(String sql) {
String from = sql.substring(sql.indexOf("from") + 4);
String tableName = from.substring(0, from.indexOf("where")).trim();
return tableName;
}
//查询语句的路由分片 private Object hanlderSelectRoute(String sql, BoundSql boundSql, PreparedStatementHandler preparedStatementHandler, Invocation invocation, Field sqlF) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, SQLException { //获得表名 String tableName=getSelectName(sql); //从boundSql里面的参数映射中,找到是否有按当前路由分配规则进行分片的字段 //分场景 //存在路由分片规则 if(routeConfig.getRouteKey()!=null){ Long hashcode=0L; List<ParameterMapping> list=boundSql.getParameterMappings(); for(ParameterMapping bean:list){ //判断查询的字段是否为分片字段 if(bean.getProperty().equals(routeConfig.getRouteKey())){ hashcode= (long) boundSql.getParameterObject().toString().hashCode
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。