插件机制
- MyBatis在四大对象(Executor 、ParameterHandler 、ResultSetHandler、StatementHandler)的创建过程中,都会有插件进行介入。
创建好四大对象后,都是先执行下面这一句才返回。
代码:
interceptorChain.pluginAll(parameterHandler);
pluginAll方法:
就是获取到所有的interceptor (拦截器)(插件需要的接口),调用interceptor.plugin(target),返回target包装后的对象public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; }
- 插件机制
我们可以使用插件为目标对象创建一个代理对象。(和AOP面向切面原理)),然后代理对象就可以拦截到四大对象的每一个执行
插件编写
-
编写Interceptor的实现类
这个就是所谓的插件/** * 完成插件签名:告诉MyBatis当前插件用来拦截哪个对象的哪个方法 */ @Intercepts({ @Signature(type = StatementHandler.class,method = "parameterize",args=java.sql.Statement.class) }) public class MyFirstPlugin implements Interceptor { /** * 拦截目标对象的目标方法的执行 * @param invocation * @return * @throws Throwable */ public Object intercept(Invocation invocation) throws Throwable { //执行目标方法 Object proceed = invocation.proceed(); return proceed;//返回执行后的返回值 } /** * 包装目标对象 :为目标对象创建一个代理对象 * @param o * @return */ public Object plugin(Object o) { //Mybatis提供了创建代理对象的方法 就不用我们自己调proxy那个方法 //借助Plugin的wrap方法来使用当前拦截器Interceptor包装我们目标对象 Object wrap = Plugin.wrap(o, this); //传入要创建的目标对象,用的拦截器就是this return wrap;//返回当前目标对象的动态代理 } /** * 将插件注册时 的property属性设置进来 * @param properties */ public void setProperties(Properties properties) { System.out.println("插件配置的信息"+properties); } }
-
使用@Intercepts注解完成插件签名
告诉MyBatis当前插件用来拦截哪个对象的哪个方法@Intercepts({ @Signature(type = StatementHandler.class,method = "parameterize",args=java.sql.Statement.class)
})
```
3. 将写好的插件注册到全局配置文件中
<plugins>
<plugin interceptor="com.flora.mybatis.dao.MyFirstPlugin"></plugin>
</plugins>
运行结果
源码分析
plugin方法
调用plugin()方法的代码:
创建Executor调用的
Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
四大对象创建的时候都会调用plugin方法,会判断目标对象是否在插件签名中,在的才返回代理对象
我们具体看Plugin.wrap如何工作,wrap()方法代码如下
- 拿到当前插件的签名signatureMap
签名里就包含了要拦截哪些对象的哪些方法
- 拿到目标对象的接口
如现在是再创建Executor对象,所以拿到的是
- 判断目标对象接口是否在插件签名中
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
目标对象在插件签名中 才返回代理对象,不在就直接返回目标对象
最后对StatementHandler包装结果如下,这样每次调目标方法的时候都调的是动态代理的目标方法
多个插件同时拦截目标对象
如果写了MyFirstPlugin和MySecondPlugin 都对同一个对象StatementHandler进行包装,包装结果如下:
- 包装顺序
配置的时候先配置的MyFirstPlugin,再配置的是MySecondPlugin,所以是先用MyFirstPlugin对StatementHandler包装,得到一个动态代理对象;然后用MySecondPlugin对刚刚生成的动态代理对象再包装,生成最后的动态代理对象。 - 执行目标方法顺序
先执行Second再执行First
因为先进来外面的代理对象,所以先执行MySecondPlugin的方法,再执行MyFirstPlugin的方法,最后执行StatementHandler自己方法
应用场景:PageHelper插件进行分页
PageHelper插件官方文档
使用步骤
-
导入相关包pagehelper-x.x.x.jar 和 jsqlparser-
0.9.5.jar。 -
在MyBatis全局配置文件中配置分页插件。
-
使用PageHelper提供的方法进行分页
在调用查询方法之前用PageHelper的startPage方法,传入开始的页数和每页显示的记录数Page<Object> page = PageHelper.startPage(5, 1);
还可以通过以下方法 规定连续显示多少页
PageInfo<Employee> info = new PageInfo<>(emps, 5);
可以通过page.xxx获取信息
System.out.println("当前页码:"+page.getPageNum()); System.out.println("总记录数:"+page.getTotal()); System.out.println("每页的记录数:"+page.getPageSize()); System.out.println("总页码:"+page.getPages());*/
也可以通过info.xxx获取详情信息
System.out.println("当前页码:"+info.getPageNum()); System.out.println("总记录数:"+info.getTotal()); System.out.println("每页的记录数:"+info.getPageSize()); System.out.println("总页码:"+info.getPages()); System.out.println("是否第一页:"+info.isIsFirstPage()); System.out.println("连续显示的页码:");
总的Mybatis代码如下:
@Test
public void test01() throws IOException {
// 1、获取sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、获取sqlSession对象
SqlSession openSession = sqlSessionFactory.openSession();
try {
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Page<Object> page = PageHelper.startPage(5, 1);
List<Employee> emps = mapper.getEmps();
//传入要连续显示多少页
PageInfo<Employee> info = new PageInfo<>(emps, 5);
for (Employee employee : emps) {
System.out.println(employee);
}
/*System.out.println("当前页码:"+page.getPageNum());
System.out.println("总记录数:"+page.getTotal());
System.out.println("每页的记录数:"+page.getPageSize());
System.out.println("总页码:"+page.getPages());*/
///xxx
System.out.println("当前页码:"+info.getPageNum());
System.out.println("总记录数:"+info.getTotal());
System.out.println("每页的记录数:"+info.getPageSize());
System.out.println("总页码:"+info.getPages());
System.out.println("是否第一页:"+info.isIsFirstPage());
System.out.println("连续显示的页码:");
int[] nums = info.getNavigatepageNums();
for (int i = 0; i < nums.length; i++) {
System.out.println(nums[i]);
}
//xxxx
} finally {
openSession.close();
}
}