MybatisPlugin实现及原理

Plugins

简单理解为拦截器,既然是拦截器说白了一般都是动态代理来实现对目标方法的拦截,在前后做一些操作。
在mybatis将这种东西,称之为plugin,配置在mybatis-config.xml配置文件中,通过 标签配置。在mybatis中,可以被拦截的目标主要是:
1. StatementHandler;
2. ParameterHandler;
3. ResultSetHandler;
4. Executor;

这里实现 ByPage后缀的查询,自动执行分页操作;不需要显性的limit SQL操作;

interceptor
通过实现Interceptor接口,来自定义plugin

public interface Interceptor {// 拦截逻辑,参数是代理类Object intercept(Invocation invocation) throws Throwable;// 加载插件,一般使用Plugin.wrap(target, this);加载当前插件Object plugin(Object target);// 初始化属性void setProperties(Properties properties);
}

可以通过implements Interceptor来自定义plugin,但是仅仅这样是不行的,额外需要通过@Inteceptors和@Signature源注解来指定拦截器需要拦截的目标(类、方法、参数);

@Intercepts(value = {@Signature(type = StatementHandler.class,method = "prepare",args = {Connection.class,Integer.class}
)})
public class ThreadLocalPagePlugin implements Interceptor {/*** 这个方法是实际的拦截逻辑,我们的目的是在这里来实现分页,需要达到什么程度的使用。* 假设从ThreadLocal获取分页信息,来进行分页操作;* @param invocation* @return* @throws Throwable*/@Overridepublic Object intercept(Invocation invocation) throws Throwable {// 获取目标对象,注意StatementHandler中的属性都是protected// 不能直接访问,因此需要通过其他的方式来获取,就是MetaObject// 其基本实现是BaseStatementHandler其中最重要的属性是MappedStatment// 包含了SQL相关信息// 实际返回的是RoutingStatementHandlerStatementHandler handler = (StatementHandler) invocation.getTarget();// 获取指定对象的元信息MetaObject metaObject = MetaObject.forObject(handler,SystemMetaObject.DEFAULT_OBJECT_FACTORY,SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,new DefaultReflectorFactory());// 然后就可以通过MetaObject获取对象的属性// 获取RoutingStatementHandler->PrepareStatementHandler->BaseStatementHandler中的mappedStatement// mappedStatement 包含了Sql的信息MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");// 获取statement idString statementId = mappedStatement.getId();// 会拦截每个属性if (statementId.endsWith("ByPage")){// ByPage 表示的是分页查询BoundSql boundSql = handler.getBoundSql();String sql = boundSql.getSql();// 获取当前线程分页信息Page pager =  ThreadLocalUtil.threadLocal.get();String countSql = "SELECT COUNT(*) " + sql.substring(sql.indexOf("from"));Connection conn = (Connection) invocation.getArgs()[0];PreparedStatement ps = conn.prepareStatement(countSql);// 获取参数处理器来处理参数ParameterHandler ph = (ParameterHandler) metaObject.getValue("delegate.parameterHandler");ph.setParameters(ps);// 执行查询ResultSet rs = ps.executeQuery();if(rs.next()){pager.setTotalCount(rs.getInt(1));}String pageSql = sql + " LIMIT " + pager.getStartPos() + ", " + pager.getPageSize();metaObject.setValue("delegate.boundSql.sql", pageSql);}return invocation.proceed();}// 指定需要拦截的对象@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}// 初始化属性@Overridepublic void setProperties(Properties properties) {}
}

增加配置:


测试代码:

    Page pager = new Page(1, 2);ThreadLocalUtil.threadLocal.set(pager);List list = getSqlSession().getMapper(UserMapper.class).selectUserByPage();Page page = ThreadLocalUtil.threadLocal.get();System.out.println(list.size()+page.toString());

打印出分页相关信息。这里没有将查询结果设置到 page 对象中。

----------- intercept query end… ---------testPro
2Pager{startPos=0, curPage=1, pageSize=2, datas=null, totalPage=3, totalCount=5}

我们再增加一个处理查询结果的Plugin代码如下,该代码拦截结果处理的方法,并将处理后的结果设置到我们的 Page对象中

@Intercepts(value = {@Signature(type = ResultSetHandler.class,method = "handleResultSets",args = {Statement.class}
)})
public class ThreadLocalPagePluginAfter implements Interceptor {/*** 这个方法是实际的拦截逻辑,我们的目的是在这里来实现分页,需要达到什么程度的使用。* 假设从ThreadLocal获取分页信息,来进行分页操作;* @param invocation* @return* @throws Throwable*/@Overridepublic Object intercept(Invocation invocation) throws Throwable {Object rr = invocation.proceed();Page page =ThreadLocalUtil.threadLocal.get();page.setDatas((List) rr);return rr;}// 指定需要拦截的对象@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}// 初始化属性@Overridepublic void setProperties(Properties properties) {}
}

配置plugin

执行测试代码:

 public static void main(String[] args) throws Exception {SqlSession sesion =  getSqlSession();UserMapper mapper= sesion.getMapper(UserMapper.class);Page page = new Page<>(1,2);ThreadLocalUtil.threadLocal.set(page);mapper.getUsersByPage();System.out.println(page);}

可以看到 我们拿到了分页的完整信息

11:22:20.796 [main] DEBUG c.t.m.m.UserMapper.getUsersByPage - ==>  Preparing: SELECT COUNT(*) from user 
11:22:20.822 [main] DEBUG c.t.m.m.UserMapper.getUsersByPage - ==> Parameters: 
11:22:20.857 [main] DEBUG c.t.m.m.UserMapper.getUsersByPage - ==>  Preparing: select * from user LIMIT 0, 2 
11:22:20.858 [main] DEBUG c.t.m.m.UserMapper.getUsersByPage - ==> Parameters: 
11:22:20.863 [main] DEBUG c.t.m.m.UserMapper.getUsersByPage - <==      Total: 2
Pager{startPos=0, curPage=1, pageSize=2, datas=[com.test.mybatis.beans.User@5b8dfcc1, com.test.mybatis.beans.User@2f9f7dcf], totalPage=7502, totalCount=15003}

Plugin实现原理
1.我们我们在配置文件中声明定义了plugin 如:

  

2.当Mybaitis 启动加载配置信息时将配置的plugin加载到 Configuration 属性 中
在这里插入图片描述
3、随后在创建 一下四个接口的实现类之后会调用 InterceptorChain 的 pluginAll方法,这里以 Exexutor 为例如截图:
1. StatementHandler;
2. ParameterHandler;
3. ResultSetHandler;
4. Executor;
在这里插入图片描述

4、pluginAll方法 调用所有plugin 对象的 plugin方法 如下图:
在这里插入图片描述
以我们实现的plugin 为例 可以看到 plugin 方法只有 一行 调用 Plugin.wrap参数target是我们们的 Executor实例或代理对象,

return Plugin.wrap(target, this);

wrap 方法中判断我们定义的plugin 是否对目标对象( Executor实例或代理对象)的接口做了拦截,如果做了拦截 就用当前我们定义的Interceptor 构造一个plugin 对 目标对象做代理,否则不做代理。
在这里插入图片描述
经过上述过程如果 我们定义的 多个plugin 对某个接口做了拦截,就会依次生成对象的代理。我们这里的两个plugin 分别 对 Executor接口和resultSetHandler接口做了代理,所以创建 executor对象后就会有一个plugin 对 这个对象做代理。resultSetHandler同理。所以如果定义了 plugin 那么我们得到的 对象其实是代理对象。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部