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 那么我们得到的 对象其实是代理对象。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
