当前位置:   article > 正文

Mybatis自定义插件实战以及与Spring整合原理_mybatis-spring configuration plugin

mybatis-spring configuration plugin


前言

学习一个框架除了搞清楚核心原理外,还有很关键的需要知道如何去扩展它,而对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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

同时定义一个分片规则配置类,其中routeKey为分片字段,决定当前执行的SQL语句是否需要分片

@ConfigurationProperties(prefix="route")
@Component
@Data
public class RouteConfg {
   
   
	private boolean enabled=true;
	//分片字段
	private String routeKey;
	//分片表的数量
	private int tableCount;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

接下来,重写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();
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
//获得实例中指定name的field
public Field getField(Object obj,String fieldName){
   
	Field field=ReflectionUtils.findField(obj.getClass(), fieldName);
	//设置访问权限
	ReflectionUtils.makeAccessible(field);
	return field;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

接下来,根据不同的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();
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

查询的分片处理流程: 先根据执行的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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
//查询语句的路由分片
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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/寸_铁/article/detail/892540
推荐阅读
相关标签
  

闽ICP备14008679号