长沙小升初有什么做试卷的网站,wordpress相册列表,做网站用什么语言简单,网络教育全程托管目录
分页插件
Mybatis插件典型适用场景
实现思考
第一个问题
第二个问题
自定义分页插件
分页插件使用
添加pom依赖
插件注册
调用
代理和拦截是怎么实现的
PageHelper 原理 分页插件
MyBatis 通过提供插件机制#xff0c;让我们可以根据自己的需要去增强MyBati…目录
分页插件
Mybatis插件典型适用场景
实现思考
第一个问题
第二个问题
自定义分页插件
分页插件使用
添加pom依赖
插件注册
调用
代理和拦截是怎么实现的
PageHelper 原理 分页插件
MyBatis 通过提供插件机制让我们可以根据自己的需要去增强MyBatis 的功能。需要注意的是如果没有完全理解MyBatis 的运行原理和插件的工作方式最好不要使用插件因为它会改变系底层的工作逻辑给系统带来很大的影响。
MyBatis 的插件可以在不修改原来的代码的情况下通过拦截的方式改变四大核心对象的行为比如处理参数处理SQL处理结果。
Mybatis插件典型适用场景
分页功能
mybatis的分页默认是基于内存分页的查出所有再截取数据量大的情况下效率较低不过使用mybatis插件可以改变该行为只需要拦截StatementHandler类的prepare方法改变要执行的SQL语句为分页语句即可
公共字段统一赋值
一般业务系统都会有创建者创建时间修改者修改时间四个字段对于这四个字段的赋值实际上可以在DAO层统一拦截处理可以用mybatis插件拦截Executor类的update方法对相关参数进行统一赋值即可
性能监控
对于SQL语句执行的性能监控可以通过拦截Executor类的update, query等方法用日志记录每个方法执行的时间
其它
其实mybatis扩展性还是很强的基于插件机制基本上可以控制SQL执行的各个阶段如执行阶段参数处理阶段语法构建阶段结果集处理阶段具体可以根据项目业务来实现对应业务逻辑。
实现思考
第一个问题 不修改对象的代码怎么对对象的行为进行修改比如说在原来的方法前面做一点事情在原来的方法后面做一点事情 答案大家很容易能想到用代理模式这个也确实是MyBatis 插件的原理。
第二个问题
我们可以定义很多的插件那么这种所有的插件会形成一个链路比如我们提交一个休假申请先是项目经理审批然后是部门经理审批再是HR 审批再到总经理审批怎么实现层层的拦截 答案插件是层层拦截的我们又需要用到另一种设计模式——责任链模式。
在之前的源码中我们也发现了mybatis内部对于插件的处理确实使用的代理模式既然是代理模式我们应该了解MyBatis 允许哪些对象的哪些方法允许被拦截并不是每一个运行的节点都是可以被修改的。只有清楚了这些对象的方法的作用当我们自己编写插件的时候才知道从哪里去拦截。在MyBatis 官网有答案我们来看一下
mybatis – MyBatis 3 | 配置 Executor 会拦截到CachingExcecutor 或者BaseExecutor。因为创建Executor 时是先创建CachingExcecutor再包装拦截。从代码顺序上能看到。我们可以通过mybatis的分页插件来看看整个插件从包装拦截器链到执行拦截器链的过程。 在查看插件原理的前提上我们需要来看看官网对于自定义插件是怎么来做的官网上有介绍通过 MyBatis 提供的强大机制使用插件是非常简单的只需实现 Interceptor 接口并指定想要拦截的方法签名即可。这里本人踩了一个坑在Springboot中集成同时引入了pagehelper-spring-boot-starter 导致RowBounds参数的值被刷掉了也就是走到了我的拦截其中没有被设置值这里需要注意拦截器出了问题可以Debug看一下Configuration配置类中拦截器链的包装情况。
自定义分页插件
Intercepts({Signature(type Executor.class,method query ,args {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} ), // 需要代理的对象和方法Signature(type Executor.class,method query ,args {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class} ) // 需要代理的对象和方法
})
public class MyPageInterceptor implements Interceptor {Overridepublic Object intercept(Invocation invocation) throws Throwable {System.out.println(简易版的分页插件逻辑分页改成物理分页);// 修改sql 拼接Limit 0,10Object[] args invocation.getArgs();// MappedStatement 对mapper映射文件里面元素的封装MappedStatement ms (MappedStatement) args[0];// BoundSql 对sql和参数的封装Object parameterObjectargs[1];BoundSql boundSql ms.getBoundSql(parameterObject);// RowBounds 封装了逻辑分页的参数 当前页offset一页数limitRowBounds rowBounds (RowBounds) args[2];// 拿到原来的sql语句String sql boundSql.getSql();String limitSqlsql limit rowBounds.getOffset(), rowBounds.getLimit();//将分页sql重新封装一个BoundSql 进行后续执行BoundSql pageBoundSql new BoundSql(ms.getConfiguration(), limitSql, boundSql.getParameterMappings(), parameterObject);// 被代理的对象Executor executor (Executor) invocation.getTarget();CacheKey cacheKey executor.createCacheKey(ms, parameterObject, rowBounds, pageBoundSql);// 调用修改过后的sql继续执行查询return executor.query(ms,parameterObject,rowBounds, (ResultHandler) args[3],cacheKey,pageBoundSql);}
}
拦截签名跟参数的顺序有严格要求如果按照顺序找不到对应方法会抛出异常 org.apache.ibatis.exceptions.PersistenceException: ### Error opening session. Cause: org.apache.ibatis.plugin.PluginException: Could not find method on interface org.apache.ibatis.executor.Executor named query MyBatis 启动时扫描 标签 注册到Configuration 对象的 InterceptorChain 中。property 里面的参数会调用setProperties()方法处理。
分页插件使用
添加pom依赖
dependencygroupIdcom.github.pagehelper/groupIdartifactIdpagehelper/artifactIdversion1.2.15/version
/dependency
插件注册
在mybatis-config.xml 中注册插件
configurationplugins!-- com.github.pagehelper为PageHelper类所在包名 --plugin interceptorcom.github.pagehelper.PageHelperproperty namehelperDialect valuemysql /!-- 该参数默认为false --!-- 设置为true时会将RowBounds第一个参数offset当成pageNum页码使用 --!-- 和startPage中的pageNum效果一样 --property nameoffsetAsPageNum valuetrue /!-- 该参数默认为false --!-- 设置为true时使用RowBounds分页会进行count查询 --property namerowBoundsWithCount valuetrue /!-- 设置为true时如果pageSize0或者RowBounds.limit 0就会查询出全部的结果 --!-- 相当于没有执行分页查询但是返回结果仍然是Page类型 --property namepageSizeZero valuetrue /!-- 3.3.0版本可用 - 分页参数合理化默认false禁用 --!-- 启用合理化时如果pageNum1会查询第一页如果pageNumpages会查询最后一页 --!-- 禁用合理化时如果pageNum1或pageNumpages会返回空数据 --property namereasonable valuetrue /!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 --!-- 增加了一个params参数来配置参数映射用于从Map或ServletRequest中取值 --!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值 --!-- 不理解该含义的前提下不要随便复制该配置 --property nameparams valuepageNumstart;pageSizelimit; //plugin/plugins
/configuration
调用
// 获取配置文件
InputStream inputStream Resources.getResourceAsStream(mybatis/mybatis-config.xml);
// 通过加载配置文件获取SqlSessionFactory对象
SqlSessionFactory factory new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession sqlSession sqlSessionFactory.openSession()) {// Mybatis在getMapper就会给我们创建jdk动态代理EmpMapper mapper sqlSession.getMapper(EmpMapper.class);PageHelper.startPage(1, 5);ListEmp listmapper.selectAll(); PageInfoServiceStation info new PageInfoServiceStation(list, 3); System.out.println(当前页码info.getPageNum());System.out.println(每页的记录数info.getPageSize());System.out.println(总记录数info.getTotal());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]);}
}
代理和拦截是怎么实现的
上面提到的可以被代理的四大对象都是什么时候被代理的呢Executor 是openSession() 的时候创建的 StatementHandler 是SimpleExecutor.doQuery()创建的里面包含了处理参数的ParameterHandler 和处理结果集的ResultSetHandler 的创建创建之后即调用InterceptorChain.pluginAll()返回层层代理后的对象。代理是由Plugin 类创建。在我们重写的 plugin() 方法里面可以直接调用returnPlugin.wrap(target, this);返回代理对象。
当个插件的情况下代理能不能被代理代理顺序和调用顺序的关系 可以被代理。 因为代理类是Plugin所以最后调用的是Plugin 的invoke()方法。它先调用了定义的拦截器的intercept()方法。可以通过invocation.proceed()调用到被代理对象被拦截的方法。 调用流程时序图 PageHelper 原理
先来看一下分页插件的简单用法 PageHelper.startPage(1, 3); ListBlog blogs blogMapper.selectBlogById2(blog); PageInfo page new PageInfo(blogs, 3); 对于插件机制我们上面已经介绍过了在这里我们自然的会想到其所涉及的核心类 PageInterceptor。拦截的是Executor 的两个query()方法要实现分页插件的功能肯定是要对我们写的sql进行改写那么一定是在 intercept 方法中进行操作的我们会发现这么一行代码
String pageSql this.dialect.getPageSql(ms, boundSql, parameter, rowBounds, cacheKey); 调用到 AbstractHelperDialect 中的 getPageSql 方法
public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {// 获取sqlString sql boundSql.getSql();//获取分页参数对象Page page this.getLocalPage();return this.getPageSql(sql, page, pageKey);}
这里可以看到会去调用 this.getLocalPage()我们来看看这个方法
public T PageT getLocalPage() {return PageHelper.getLocalPage();
}
//线程独享
protected static final ThreadLocalPage LOCAL_PAGE new ThreadLocal();
public static T PageT getLocalPage() {return (Page)LOCAL_PAGE.get();
}
可以发现这里是调用的是PageHelper的一个本地线程变量中的一个 Page对象从其中获取我们所设置的 PageSize 与 PageNum那么他是怎么设置值的呢请看
PageHelper.startPage(1, 3);public static E PageE startPage(int pageNum, int pageSize) {return startPage(pageNum, pageSize, true);
}public static E PageE startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {PageE page new Page(pageNum, pageSize, count);page.setReasonable(reasonable);page.setPageSizeZero(pageSizeZero);PageE oldPage getLocalPage();if (oldPage ! null oldPage.isOrderByOnly()) {page.setOrderBy(oldPage.getOrderBy());}//设置页数行数信息setLocalPage(page);return page;
}protected static void setLocalPage(Page page) {//设置值LOCAL_PAGE.set(page);
}
在我们调用 PageHelper.startPage(1, 3); 的时候系统会调用 LOCAL_PAGE.set(page) 进行设置从而在分页插件中可以获取到这个本地变量对象中的参数进行 SQL 的改写由于改写有很多实现我们这里用的Mysql的实现 在这里我们会发现分页插件改写SQL的核心代码这个代码就很清晰了不必过多赘述
public String getPageSql(String sql, Page page, CacheKey pageKey) {StringBuilder sqlBuilder new StringBuilder(sql.length() 14);sqlBuilder.append(sql);if (page.getStartRow() 0) {sqlBuilder.append( LIMIT );sqlBuilder.append(page.getPageSize());} else {sqlBuilder.append( LIMIT );sqlBuilder.append(page.getStartRow());sqlBuilder.append(,);sqlBuilder.append(page.getPageSize());pageKey.update(page.getStartRow());}pageKey.update(page.getPageSize());return sqlBuilder.toString();
}
PageHelper 就是这么一步一步的改写了我们的SQL 从而达到一个分页的效果。
关键类总结