博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
MyBatis插件开发:简单分页插件
阅读量:6231 次
发布时间:2019-06-21

本文共 13886 字,大约阅读时间需要 46 分钟。

原文地址:

MyBatis插件开发流程

  • 类实现Interceptor接口;
  • 类上添加注解@Intercepts({@Signature(type, method, args)})
    • type:需要拦截的对象,只可取四大对象之一Executor.class、StatementHandler.class、ParameterHandler.class、ResultSetHandler.class
    • method:拦截的对象方法。
    • args:拦截的对象方法参数。
  • 实现拦截的方法Object intercept(Invocation invocation)

Interceptor接口

public interface Interceptor {    /**     * 此方法将直接覆盖被拦截对象的原有方法     *     * @param invocation 通过该对象可以反射调度拦截对象的方法     * @return     * @throws Throwable     */    Object intercept(Invocation invocation) throws Throwable;    /**     * 为被拦截对象生成一个代理对象,并返回它     *     * @param target 被拦截的对象     * @return     */    Object plugin(Object target);    /**     * 设置插件配置的参数     *     * @param properties 插件配置的参数     */    void setProperties(Properties properties);}复制代码

简单分页插件开发

确定拦截的方法签名

需要在实现Interceptor接口的类上加入@Intercepts({@Signature(type, method, args)})注解才能够运行插件。

type-拦截的对象

  • Executor 执行的SQL 全过程,包括组装参数、组装结果返回和执行SQL的过程等都可以拦截。
  • StatementHandler 执行SQL的过程,拦截该对象可以重写执行SQL的过程。
  • ParameterHandler 执行SQL 的参数组装,拦截该对象可以重写组装参数的规则。
  • ResultSetHandler 执行结果的组装,拦截该对象可以重写组装结果的规则。

对于分页插件,我们只需要拦截StatementHandler对象,重写SELECT类型的SQL语句,实现分页功能。

method-拦截的方法

我们已经能够确定拦截的对象是StatementHandler了,现在我们要确定拦截的是哪个方法,因为StatementHandler是通过prepare方法对SQL进行预编译的,所以我们需要对prepare方法进行拦截,在这个方法执行之前,完成SQL的重新编写(加入limit)。

StatementHandler

public interface StatementHandler {  /**   * 预编译SQL   *   * @param connection   * @return   * @throws SQLException   */  Statement prepare(Connection connection)      throws SQLException;  /**   * 设置参数   *   * @param statement   * @throws SQLException   */  void parameterize(Statement statement)      throws SQLException;  /**   * 批处理   *   * @param statement   * @throws SQLException   */  void batch(Statement statement)      throws SQLException;  /**   * 执行更新操作   *   * @param statement   * @return 返回影响行数   * @throws SQLException   */  int update(Statement statement)      throws SQLException;  /**   * 执行查询操作,将结果交给ResultHandler进行结果的组装   *   * @param statement   * @param resultHandler   * @param 
* @return 返回查询的数据列表 * @throws SQLException */
List
query(Statement statement, ResultHandler resultHandler) throws SQLException; /** * 得到绑定的sql * * @return */ BoundSql getBoundSql(); /** * 得到参数处理器 * * @return */ ParameterHandler getParameterHandler();}复制代码

args-拦截的参数

args是一个Class类型的数组,表示的是被拦截方法的参数列表。由于我们已经确定了拦截的是StatementHandlerprepare方法,而该方法只有一个参数Connection,所以我们只需要拦截这一个参数即可。

实现拦截方法

定义一个封装分页参数的类Page

package cn.xeblog.pojo;public class Page {    /**     * 当前页码     */    private Integer pageIndex;    /**     * 每页数据条数     */    private Integer pageSize;    /**     * 总数据数     */    private Integer total;    /**     * 总页数     */    private Integer totalPage;    public Page() {    }    public Page(Integer pageIndex, Integer pageSize) {        this.pageIndex = pageIndex;        this.pageSize = pageSize;    }	// 省略get、set方法...}复制代码

实现插件分页的功能

package cn.xeblog.plugin;import cn.xeblog.pojo.Page;import org.apache.ibatis.executor.parameter.ParameterHandler;import org.apache.ibatis.executor.statement.StatementHandler;import org.apache.ibatis.mapping.BoundSql;import org.apache.ibatis.mapping.MappedStatement;import org.apache.ibatis.plugin.*;import org.apache.ibatis.reflection.MetaObject;import org.apache.ibatis.reflection.SystemMetaObject;import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;import org.apache.ibatis.session.Configuration;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;import java.util.Map;import java.util.Properties;import java.util.Set;@Intercepts({
@Signature( type = StatementHandler.class, method = "prepare", args = {Connection.class})})public class PagingPlugin implements Interceptor { /** * 默认页码 */ private Integer defaultPageIndex; /** * 默认每页数据条数 */ private Integer defaultPageSize; public Object intercept(Invocation invocation) throws Throwable { StatementHandler statementHandler = getUnProxyObject(invocation); MetaObject metaObject = SystemMetaObject.forObject(statementHandler); String sql = getSql(metaObject); if (!checkSelect(sql)) { // 不是select语句,进入责任链下一层 return invocation.proceed(); } BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql"); Object parameterObject = boundSql.getParameterObject(); Page page = getPage(parameterObject); if (page == null) { // 没有传入page对象,不执行分页处理,进入责任链下一层 return invocation.proceed(); } // 设置分页默认值 if (page.getPageIndex() == null) { page.setPageIndex(this.defaultPageIndex); } if (page.getPageSize() == null) { page.setPageSize(this.defaultPageSize); } // 设置分页总数,数据总数 setTotalToPage(page, invocation, metaObject, boundSql); // 校验分页参数 checkPage(page); return changeSql(invocation, metaObject, boundSql, page); } public Object plugin(Object target) { // 生成代理对象 return Plugin.wrap(target, this); } public void setProperties(Properties properties) { // 初始化配置的默认页码,无配置则默认1 this.defaultPageIndex = Integer.parseInt(properties.getProperty("default.pageIndex", "1")); // 初始化配置的默认数据条数,无配置则默认20 this.defaultPageSize = Integer.parseInt(properties.getProperty("default.pageSize", "20")); } /** * 从代理对象中分离出真实对象 * * @param invocation * @return */ private StatementHandler getUnProxyObject(Invocation invocation) { // 取出被拦截的对象 StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); MetaObject metaStmtHandler = SystemMetaObject.forObject(statementHandler); Object object = null; // 分离代理对象 while (metaStmtHandler.hasGetter("h")) { object = metaStmtHandler.getValue("h"); metaStmtHandler = SystemMetaObject.forObject(object); } return object == null ? statementHandler : (StatementHandler) object; } /** * 判断是否是select语句 * * @param sql * @return */ private boolean checkSelect(String sql) { // 去除sql的前后空格,并将sql转换成小写 sql = sql.trim().toLowerCase(); return sql.indexOf("select") == 0; } /** * 获取分页参数 * * @param parameterObject * @return */ private Page getPage(Object parameterObject) { if (parameterObject == null) { return null; } if (parameterObject instanceof Map) { // 如果传入的参数是map类型的,则遍历map取出Page对象 Map
parameMap = (Map
) parameterObject; Set
keySet = parameMap.keySet(); for (String key : keySet) { Object value = parameMap.get(key); if (value instanceof Page) { // 返回Page对象 return (Page) value; } } } else if (parameterObject instanceof Page) { // 如果传入的是Page类型,则直接返回该对象 return (Page) parameterObject; } // 初步判断并没有传入Page类型的参数,返回null return null; } /** * 获取数据总数 * * @param invocation * @param metaObject * @param boundSql * @return */ private int getTotal(Invocation invocation, MetaObject metaObject, BoundSql boundSql) { // 获取当前的mappedStatement对象 MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); // 获取配置对象 Configuration configuration = mappedStatement.getConfiguration(); // 获取当前需要执行的sql String sql = getSql(metaObject); // 改写sql语句,实现返回数据总数 $_paging取名是为了防止数据库表重名 String countSql = "select count(*) as total from (" + sql + ") $_paging"; // 获取拦截方法参数,拦截的是connection对象 Connection connection = (Connection) invocation.getArgs()[0]; PreparedStatement pstmt = null; int total = 0; try { // 预编译查询数据总数的sql语句 pstmt = connection.prepareStatement(countSql); // 构建boundSql对象 BoundSql countBoundSql = new BoundSql(configuration, countSql, boundSql.getParameterMappings(), boundSql.getParameterObject()); // 构建parameterHandler用于设置sql参数 ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, boundSql.getParameterObject(), countBoundSql); // 设置sql参数 parameterHandler.setParameters(pstmt); //执行查询 ResultSet rs = pstmt.executeQuery(); while (rs.next()) { total = rs.getInt("total"); } } catch (SQLException e) { e.printStackTrace(); } finally { if (pstmt != null) { try { pstmt.close(); } catch (SQLException e) { e.printStackTrace(); } } } // 返回总数据数 return total; } /** * 设置总数据数、总页数 * * @param page * @param invocation * @param metaObject * @param boundSql */ private void setTotalToPage(Page page, Invocation invocation, MetaObject metaObject, BoundSql boundSql) { // 总数据数 int total = getTotal(invocation, metaObject, boundSql); // 计算总页数 int totalPage = total / page.getPageSize(); if (total % page.getPageSize() != 0) { totalPage = totalPage + 1; } page.setTotal(total); page.setTotalPage(totalPage); } /** * 校验分页参数 * * @param page */ private void checkPage(Page page) { // 如果当前页码大于总页数,抛出异常 if (page.getPageIndex() > page.getTotalPage()) { throw new RuntimeException("当前页码[" + page.getPageIndex() + "]大于总页数[" + page.getTotalPage() + "]"); } // 如果当前页码小于总页数,抛出异常 if (page.getPageIndex() < 1) { throw new RuntimeException("当前页码[" + page.getPageIndex() + "]小于[1]"); } } /** * 修改当前查询的sql * * @param invocation * @param metaObject * @param boundSql * @param page * @return */ private Object changeSql(Invocation invocation, MetaObject metaObject, BoundSql boundSql, Page page) throws Exception { // 获取当前查询的sql String sql = getSql(metaObject); // 修改sql,$_paging_table_limit取名是为了防止数据库表重名 String newSql = "select * from (" + sql + ") $_paging_table_limit limit ?, ?"; // 设置当前sql为修改后的sql setSql(metaObject, newSql); // 获取PreparedStatement对象 PreparedStatement pstmt = (PreparedStatement) invocation.proceed(); // 获取sql的总参数个数 int parameCount = pstmt.getParameterMetaData().getParameterCount(); // 设置分页参数 pstmt.setInt(parameCount - 1, (page.getPageIndex() - 1) * page.getPageSize()); pstmt.setInt(parameCount, page.getPageSize()); return pstmt; } /** * 获取当前查询的sql * * @param metaObject * @return */ private String getSql(MetaObject metaObject) { return (String) metaObject.getValue("delegate.boundSql.sql"); } /** * 设置当前查询的sql * * @param metaObject */ private void setSql(MetaObject metaObject, String sql) { metaObject.setValue("delegate.boundSql.sql", sql); }}复制代码

配置分页插件

mybatis-config.xml配置文件中配置自定义的分页插件

复制代码

实现DAO

定义POJO对象Role

public class Role {   private Long id;   private String roleName;   private String note;   // 省略get、set...}复制代码

定义Mapper接口,通过分页对象查询角色列表

public interface RoleMapper {    List
listRoleByPage(Page page);}复制代码

定义Mapper.xml编写查询的SQL语句

复制代码

测试分页插件

测试代码

@Testpublic void test() {	InputStream inputStream = null;	SqlSessionFactory sqlSessionFactory;	SqlSession sqlSession = null;	try {		inputStream = Resources.getResourceAsStream("mybatis-config.xml");		sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);		sqlSession = sqlSessionFactory.openSession();		RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);		// 分页参数,从第一页开始,每页显示5条数据		Page page = new Page(1, 5);		List
roleList = roleMapper.listRoleByPage(page); System.out.println("===分页信息==="); System.out.println("当前页码:" + page.getPageIndex()); System.out.println("每页显示数据数:" + page.getPageSize()); System.out.println("总数据数:" + page.getTotal()); System.out.println("总页数:" + page.getTotalPage()); System.out.println("============="); System.out.println("===数据列表==="); for (Role role : roleList) { System.out.println(role); } } catch (IOException e) { e.printStackTrace(); } finally { if (sqlSession != null) { sqlSession.close(); } if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }}复制代码

数据库role表中的全部数据信息

id role_name note
1 SUPER_ADMIN 超级管理员
2 admin 管理员
3 user 用户
4 user2 用户2
8 user3 用户3
9 test 测试
10 test2 测试2
11 test3 测试3
12 test4 测试4
13 test5 测试5

代码执行结果

===分页信息===当前页码:1每页显示数据数:5总数据数:10总页数:2================数据列表===Role{id=1, roleName='SUPER_ADMIN', note=' 超级管理员'}Role{id=2, roleName='admin', note='管理员'}Role{id=3, roleName='user', note='用户'}Role{id=4, roleName='user2', note='用户2'}Role{id=8, roleName='user3', note='用户3'}复制代码

打印的SQL信息

==>  Preparing: select count(*) as total from (SELECT id, role_name, note FROM role) $_paging ==> Parameters: <==    Columns: total<==        Row: 10<==      Total: 1==>  Preparing: select * from (SELECT id, role_name, note FROM role) $_paging_table_limit limit ?, ? ==> Parameters: 0(Integer), 5(Integer)<==    Columns: id, role_name, note<==        Row: 1, SUPER_ADMIN,  超级管理员<==        Row: 2, admin, 管理员<==        Row: 3, user, 用户<==        Row: 4, user2, 用户2<==        Row: 8, user3, 用户3<==      Total: 5复制代码

参考

  • 《深入浅出MyBatis技术原理与实战》

转载于:https://juejin.im/post/5d08bcbce51d4556bb4cd392

你可能感兴趣的文章
基于http协议使用protobuf进行前后端交互
查看>>
bash腳本編程之三 条件判断及算数运算
查看>>
php cookie
查看>>
linux下redis安装
查看>>
弃 Java 而使用 Kotlin 的你后悔了吗?| kotlin将会是最好的开发语言
查看>>
JavaScript 数据类型
查看>>
量子通信和大数据最有市场突破前景
查看>>
对‘初学者应该选择哪种编程语言’的回答——计算机达人成长之路(38)
查看>>
如何申请开通微信多客服功能
查看>>
Sr_C++_Engineer_(LBS_Engine@Global Map Dept.)
查看>>
非监督学习算法:异常检测
查看>>
jquery的checkbox,radio,select等方法总结
查看>>
Linux coredump
查看>>
Ubuntu 10.04安装水晶(Mercury)无线网卡驱动
查看>>
Myeclipes快捷键
查看>>
ToRPC:一个双向RPC的Python实现
查看>>
我的友情链接
查看>>
nginx在reload时候报错invalid PID number
查看>>
神经网络和深度学习-第二周神经网络基础-第二节:Logistic回归
查看>>
Myeclipse代码提示及如何设置自动提示
查看>>