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

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执行原理

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

 

 

 

通过刚才所讲,我们如果想要实现分页插件,那么我们需要在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接口,查看在访问数据库时是否执行了我们的拦截器

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

 

 

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

 

 

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

六、分页插件

思路:

截取原始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();// 继续执行(放行)
    }

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

 

 

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);

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

 

 

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();
            
        }

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

 

 

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

数据库记录:

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

 

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

 

 

 

上一篇:Dubbo核心源码剖析(集群 集群容错 负载均衡 服务治理 通信协议)


下一篇:使用 Castle 实现 AOP,以及 Autofac 集成 Castle