Mybatis插件机制及实现分页插件

一、Mybatis插件机制简介

Mybatis在四大组件(Executor,StatementHandler,ParameterHandler,ResultSetHandler)处都提供了插件机制。Mybatis通过对四大核心对象进行拦截,并增强对象的功能。本质上是通过动态代理来实现的,也就是说实际上,四大组件的对象最后都是代理对象。

1. Mybatis允许拦截的方法如下

  • 执行器Executor的update, query, commit, rollback等方法

  • SQL语法构建器StatementHandler的prepare, parameterize, batch, update, query等方法

  • 参数处理器ParameterHandler的getParameterObject, setParameterObject等方法

  • 结果处理器ResultSetHandler的handleResultSets, handlerOutputPameters等方法

2. Mybatis插件原理

在四大组件创建时,每个对象创建之后不是直接返回,而是通过调用interceptorChain.pluginAll(handler)之后再返回。

 

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
  ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
  parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
  return parameterHandler;
}

interceptorChain保存了所有的拦截器,这是在Mybatis初始化的时候创建的。

pluginAll方法就是遍历interceptorChain中的每一个inteceptor,执行它的plugin方法: 

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

 

二、实现分页插件

如果我们要拦截四大组件的某个方法,可以通过@Intercepts, @Signature注解来实现,例如拦截Executor对象的query方法可以这么实现:

@Intercepts({
    @Signature(
        type=Executor.class,
        method="query",
        args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
    )
})
public class InterceptExecutorQuery implements Interceptor{
    //......
}

然后在sqlMapConfig.xml中配置这个插件。

我们来实现一个Mybatis的分页插件,这个插件的用法是这样的:

    public Page<GJAJEntity> findPage(GJAJEntity leaveEntity, int pageNum) {
        PageHelper.startPage(pageNum, Constant.pageSize);
        caseDao.queryList(leaveEntity);
        return PageHelper.endPage();
    }
  • 调用PageHelper.startPage(pageNum, pageSize)方法,开始分页
  • 执行查询操作
  • 调用PageHelper.endPage()方法,结束分页,返回分页对象

 

1. 首先就是PageHelper这个类要实现Interceptor接口,并且标记拦截StatementHandler的prepare方法以及ResultSetHandler的handleResultSets方法

@Intercepts(
        {
                @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
                @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
        }
)
public class PageHelper implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        return null;
    }

    @Override
    public Object plugin(Object target) {
        return null;
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

2. 实现startPage和endPage方法

    /** 
     * 开始分页 
     * @param pageNum 
     * @param pageSize 
     */  
    public static void startPage(int pageNum, int pageSize) {  
        localPage.set(new Page(pageNum, pageSize));  
    }  
  
    /** 
     * 结束分页并返回结果,该方法必须被调用,否则localPage会一直保存下去,直到下一次startPage 
     * @return 
     */  
    public static Page endPage() {  
        Page page = localPage.get();  
        localPage.remove();  
        return page;  
    }  

这个localPage对象其实就是一个ThreadLocal对象,里面存储的是一个Page对象,Page对象里面保存了查询结果:

public static final ThreadLocal<Page> localPage = new ThreadLocal<Page>();  

3. 实现Interceptor接口的plugin方法

    /** 
     * 只拦截这两种类型的 
     * <br>StatementHandler 
     * <br>ResultSetHandler 
     * @param target 
     * @return 
     */  
    @Override  
    public Object plugin(Object target) {  
        if (target instanceof StatementHandler || target instanceof ResultSetHandler) {
            return Plugin.wrap(target, this);
        } else {  
            return target;  
        }  
    }  

4. 实现Interceptor接口的intercept方法

    @Override  
    public Object intercept(Invocation invocation) throws Throwable {
        if (localPage.get() == null) {  
            return invocation.proceed();  
        }  
        if (invocation.getTarget() instanceof StatementHandler) {
            StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
            MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
            // 分离代理对象链(由于目标类可能被多个拦截器拦截,从而形成多次代理,通过下面的两次循环  
            // 可以分离出最原始的的目标类)  
            while (metaStatementHandler.hasGetter("h")) {  
                Object object = metaStatementHandler.getValue("h");  
                metaStatementHandler = SystemMetaObject.forObject(object);
            }  
            // 分离最后一个代理对象的目标类  
            while (metaStatementHandler.hasGetter("target")) {  
                Object object = metaStatementHandler.getValue("target");  
                metaStatementHandler = SystemMetaObject.forObject(object);
            }  
            MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
            //分页信息if (localPage.get() != null) {  
            Page page = localPage.get();
            //Mybatis中最终执行的sql封装成了一个BoundSql对象,先获取到这个BoundSql对象  
            BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
            // 然后获取到sql语句。分页参数作为参数对象parameterObject的一个属性  
            String sql = boundSql.getSql();  
            // 重写sql  
            String pageSql = buildPageSqlMysql(sql, page);
            //重写分页sql ,再把这个sql语句赋值给BoundSql对象中的sql属性
            metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);  
            Connection connection = (Connection) invocation.getArgs()[0];  
            // 重设分页参数里的总页数等  
            setPageParameter(sql, connection, mappedStatement, boundSql, page);  
            // 将执行权交给下一个拦截器  
            return invocation.proceed();  
        } else if (invocation.getTarget() instanceof ResultSetHandler) {
            //拦截ResultSetHandler的handleResultSets方法,直接返回给Page
            Object result = invocation.proceed();  
            Page page = localPage.get();  
            page.setResult((List) result);  
            return result;  
        }  
        return null;  
    }  
  

/**
     * 修改原SQL为分页SQL mysql
     * @param sql
     * @param page
     * @return
     */
    private String buildPageSqlMysql(String sql, Page page) {
        StringBuilder pageSql = new StringBuilder(200);
        pageSql.append(sql);
        pageSql.append(" LIMIT ").append(page.getStartRow()).append(",").append(page.getEndRow()-page.getStartRow());
        return pageSql.toString();
    }
  
    /** 
     * 获取总记录数 
     * @param sql 
     * @param connection 
     * @param mappedStatement 
     * @param boundSql 
     * @param page 
     */  
    private void setPageParameter(String sql, Connection connection, MappedStatement mappedStatement,
                                  BoundSql boundSql, Page page) {
        // 记录总记录数  
        String countSql = "select count(0) from (" + sql + ")as total";
        PreparedStatement countStmt = null;  
        ResultSet rs = null;  
        try {  
            countStmt = connection.prepareStatement(countSql);  
            BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql,
                    boundSql.getParameterMappings(), boundSql.getParameterObject());  
            setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject());  
            rs = countStmt.executeQuery();  
            int totalCount = 0;  
            if (rs.next()) {  
                totalCount = rs.getInt(1);  
            }  
            page.setTotal(totalCount);
            int totalPage = totalCount / page.getPageSize() + ((totalCount % page.getPageSize() == 0) ? 0 : 1);  
            page.setPages(totalPage);  
        } catch (SQLException e) {  
            logger.error("Ignore this exception", e);  
        } finally {  
            try {  
            	if(rs != null){
            		rs.close();  
            	}
            } catch (SQLException e) {  
                logger.error("Ignore this exception", e);  
            }  
            try {  
                countStmt.close();  
            } catch (SQLException e) {  
                logger.error("Ignore this exception", e);  
            }  
        }  
    }  
  
    /** 
     * 代入参数值 
     * @param ps 
     * @param mappedStatement 
     * @param boundSql 
     * @param parameterObject 
     * @throws SQLException 
     */  
    private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,
                               Object parameterObject) throws SQLException {  
        ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
        parameterHandler.setParameters(ps);  
    } 

5. 使用PageHelper

    public Page<GJAJEntity> findPage(GJAJEntity leaveEntity, int pageNum) {
        PageHelper.startPage(pageNum, Constant.pageSize);
        caseDao.queryList(leaveEntity);
        return PageHelper.endPage();
    }

可以看到只要执行startPage和endPage两个方法,PageHelper本身不需要和数据发生耦合。

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,这里是使用Mybatis插件完成Spring Boot的分页插件的方法: 1. 首先,在pom.xml文件中添加Mybatis分页插件的依赖: ```xml <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.2.13</version> </dependency> ``` 2. 接下来,在application.properties文件中添加Mybatis分页插件的配置: ```properties # 开启分页插件 pagehelper.helperDialect=mysql pagehelper.reasonable=true pagehelper.supportMethodsArguments=true pagehelper.params=count=countSql ``` 3. 在Mapper.xml中编写SQL语句,并添加分页插件的配置: ```xml <!-- 查询用户列表 --> <select id="selectUsers" resultMap="userMap"> select * from user <where> <if test="username != null and username != ''"> and username like concat('%',#{username},'%') </if> <if test="email != null and email != ''"> and email like concat('%',#{email},'%') </if> </where> <if test="sort != null and sort != ''"> order by ${sort} </if> limit #{pageStart},#{pageSize} </select> <!-- 分页插件配置 --> <select id="selectUsers" resultMap="userMap"> select count(*) from user <where> <if test="username != null and username != ''"> and username like concat('%',#{username},'%') </if> <if test="email != null and email != ''"> and email like concat('%',#{email},'%') </if> </where> </select> ``` 4. 最后,在Service中调用Mapper方法进行分页查询: ```java @Service public class UserService { @Autowired private UserMapper userMapper; public Page<User> selectUsers(String username, String email, String sort, int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); List<User> users = userMapper.selectUsers(username, email, sort); PageInfo<User> pageInfo = new PageInfo<>(users); return PageConverter.convert(pageInfo); } } ``` 这样,就可以使用Mybatis插件完成Spring Boot的分页插件了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值