ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Mybatis-Plugin底层原理&分页插件

2021-01-14 23:33:30  阅读:282  来源: 互联网

标签:插件 拦截器 return 分页 Plugin 获取 invocation sql Mybatis


Mybatis Plugin源码解析

课程目录

1、Mybatis之拦截器

1.1 查询大量数据引发问题

1.2 分页实现方式

1.3 Mybatis运行流程

1.4 拦截器概述

2、拦截器-入门程序

3、分页插件

 

 

一、查询大量数据引发的问题?

大家在使用比较常见的ORM框架【mybatis&hibernate等】,无非就是对数据库的增删改查操作。

而且使用的大多操作都是查询,当查询数据过多的时候我们一般会采用分页的形式展示数据:

1、性能问题

每次查询都查询出所有需求的数据对性能的影响非常大。

如果你的数据库中有一百万条记录,当我只需要查看10条数据时,这里有两种案例,你可以想一下哪一种更好。

  • 查询出所有的记录,即一百万条,在编写业务代码从中获取10条记录回显到页面中

  • 只查询出10条记录回显到页面中

可想而知,肯定是第二种方式最快

2、用户体验问题

如果一次性把所有数据展示出来,那么页面中非常占地方,从而导致用户体验不好。

 

二、常见的分页实现方式

刚才讲了为什么要分页,我们来看一下有哪些实现数据分页的手段【基于MySQL和mybatis】

常见的分页实现方式:

  • 基于sql进行分页

  • 通过拦截器进行分页(推荐)

 

三、实现方式的区别

1、使用sql分页

吸取了数组分页的教训,我们发现一次性读取所有数据,然后在程序中进行二次操作得到分页数据,会非常影响性能,所以,如果我们能直接从数据库中查询出分页数据,那么就解决了【系统性能、用户体验】问题,所以,sql分页横空出世。

缺陷:虽然sql分页解决了性能、用户体验问题,但是引发了另一个问题,我们直接查询分页数据时,sql后面都需要写limit语句,而且还需要写获取count的sql语句,从而导致sql语句的冗余问题。

2、使用拦截器进行分页

一句话概括:我们只需要关注我们的业务SQL,把分页业务交给别人来做,这样我们编码方便,分页功能也实现,那么这个别人就是拦截器,也就是我们所谓的插件。
注意:拦截器只负责拦截,至于拦截的业务还需要我们去编写,所以如果我们想要编写分页插件,那么我们需要考虑的两个问题就是:
1.在哪拦截
2.拦截业务

四、Mybatis执行原理

 

 

 

通过刚才所讲,我们如果想要实现分页插件,那么我们需要在Statement Handler之后进行拦截,而且,在mybatis中,拦截器可以拦截四个对象中的方法:Executor、Statement Handler、Parameter Handler、ResultSetHandler

五、自定义拦截器之入门程序

1.创建一个类,实现Interceptor接口

public class MyPlugin implements Interceptor {}

注意:Interceptor所属的包:org.apache.ibatis.plugin.Interceptor

2.重写抽象方法

/**
    * @author 拦截器业务-核心
    * @param invocation
    * @return
    * @throws Throwable
    */
   public Object intercept(Invocation invocation) throws Throwable {
       return null;
  }

   /**
    * @author 用于提交拦截器,由拦截器链进行统一执行
    * @param o
    * @return
    */
   public Object plugin(Object o) {
       return null;
  }

   /**
    * @author 用于设置参数
    * @param properties
    */
   public void setProperties(Properties properties) {

  }

3.设置拦截对象

/**
* @Intercepts:定义拦截器注解
* @Signature:拦截器签名,用于指定拦截信息
*     type:指定拦截对象
*     method:指定拦截对象中的方法
*     args:指定方法的参数
*/
@Intercepts({@Signature(
       type = StatementHandler.class,
       method = "prepare",
       args = {Connection.class}
)})
public class MyPlugin implements Interceptor {}

4.编写拦截器业务

/**
    * @author 拦截器业务-核心
    * @param invocation
    * @return
    * @throws Throwable
    */
   public Object intercept(Invocation invocation) throws Throwable {

       System.out.println("这是拦截器,被执行了......");

       return invocation.proceed();// 继续执行(放行)
  }

5.提交拦截器

/**
    * @author 用于提交拦截器,由拦截器链进行统一执行
    * @param o :代理目标对象
    * @return
    */
   public Object plugin(Object o) {
       
       // 提交拦截器以及代理目标对象(我们所写的拦截器)
       return Plugin.wrap(o,this);
  }

6.注册拦截器

<configuration>

   <plugins>
       <plugin interceptor="com.example.plugin.MyPlugin"></plugin>
   </plugins>

</configuration>

7.测试

启动服务器,访问我们的user接口,查看在访问数据库时是否执行了我们的拦截器

 

 

 

 

通过刚刚所写的入门程序,我们实现了简单的拦截器,那么接下来我们可以想一下怎么用拦截器实现分页业务

六、分页插件

思路:

截取原始SQL语句,获取Count,拼接Limit

1.获取原始SQL语句

/**
     * @author 拦截器业务-核心
     * @param invocation
     * @return
     * @throws Throwable
     */
    public Object intercept(Invocation invocation) throws Throwable {

        // System.out.println("这是拦截器,被执行了......");
        // 1.获取原始SQL语句
        // 1.1 获取Statement Handler对象
        // (在执行过程中,Executor会通过Statement Handler创建Statement对象,那么Sql语句由Statement Handler进行管理)
        StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
        // 1.2 获取原始SQL语句  BoundSql:Sql语句对象
        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql();
        System.out.println("原始Sql:"+sql);

        return invocation.proceed();// 继续执行(放行)
    }

 

 

2.获取参数

当执行mapper时,我们会传递条件参数以及分页数据,那么分页数据我们在拼接Limit时会用到

这里我封装了一个Page对象,当传递参数时,通过Map集合的方式进行传递,比如:

需求:

	查询性别为男性的所有用户信息,每页显示10条记录

传递参数:
    Page page = new Page();
	User user = new User();
	Map<String,Object> map = HashMap<String,Object>();
	map.put("page",page);
	map.put("user",user);
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Page {


    private int pageCount;// 总页数
    
    private int thisPage;// 当前页
    
    private int pageRow;// 每页行数
    
    private int rowCount;// 总行数
}

3.获取StatementId

  
        // 2.获取参数
        Map<String,Object> map = (Map<String,Object>) boundSql.getParameterObject();
       
        // 3.获取方法名,判断是否分页(其实获取的是Mapped Statement中的StatementId)
        // 3.1获取Mapped Statement对象
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        // 3.2获取StatementId 
        String statementId = mappedStatement.getId();
        System.out.println("方法名:"+statementId);

 

 

4.是否分页

// 4.判断方法名是否以ByPage结尾
// true:分页   false:不分页
if(statementId.matches(".*ByPage$")){}

5.获取Count

        // 4.判断方法名是否以ByPage结尾
        // true:分页   false:不分页
        if(statementId.matches(".*ByPage$")){
            // 5.获取Count
            // 5.1定义countSql
            String countSql = "select count(0) from ("+sql+") a";
            // 5.2利用JDBC操作数据库【可以封装方法】
            Connection connection = (Connection) invocation.getArgs()[0];
            PreparedStatement preparedStatement = connection.prepareStatement(countSql);

            // 注:为了防止有些时候原始sql中需要参数,我们需要获取Parameter Handler对象
            ParameterHandler parameterHandler = (ParameterHandler) metaObject.getValue("delegate.parameterHandler");
            parameterHandler.setParameters(preparedStatement);
            
            ResultSet resultSet = preparedStatement.executeQuery();
            if(resultSet.next()){
                // 把count设置到Page对象中
                page.setRowCount(resultSet.getInt(1));
            }
            
            System.out.println("Page对象:"+page);
            
            resultSet.close();
            preparedStatement.close();
            
        }

 

 

6.拼接limit

            // 6.拼接limit【可以封装方法】
            StringBuffer sb = new StringBuffer();
            sb.append(sql);
            // limit:当前页数-1*每页行数【可以封装方法到Page对象中】
            sb.append(" limit "+((page.getThisPage()-1)*page.getPageRow()) + " , "+page.getPageRow());

            // 把sql设置到上下文中继续执行
            metaObject.setValue("delegate.boundSql.sql",sb.toString());

        }

7.测试

当前页数:1 下一页:2 每页记录数:2

数据库记录:

 

 

 

 

 

标签:插件,拦截器,return,分页,Plugin,获取,invocation,sql,Mybatis
来源: https://www.cnblogs.com/LPWGWW/p/14280027.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有