Mybatisplus源码解析——分页插件

文章讲述了如何在Mybatis-plus中通过自定义Mapper接口继承BaseMapper来实现分页查询,包括使用`selectPage()`函数,以及MapperProxy类在分页处理中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

分页功能实现

自定义的Mapper接口的继承于BaseMapper,这样进行分页时只需要调用BaseMapper接口的selectPage()函数即可,样例代码如下:

// 自定义的Mapper接口,继承于BaseMapper<>接口
public interface ProdValuationMapper extends BaseMapper<ProdValuation> {
    List<ProdValuationIndicatorDTO> getProdValuationIndicators(@Param("query") ProductValuationQuery query);
}

调用ProdValuationMapper的selectPage()函数进行分页查询的代码如下:

@Component
public class ProdRiskHelper {
    @Autowired
    private ProdValuationMapper prodValuationMapper;

    /**
     * 获取最新日期
     *
     * @param portfolioNos 组合编号
     * @param endDate      结束日期
     * @return 最新日期
     */
    public LocalDate getLastValuationdate(List<String> portfolioNos, LocalDateTime endDate) {
        // 创建一个LambdaQueryWrapper对象,用于封装业务查询条件和排序规则
        final LambdaQueryWrapper<ProdValuation> wrapper = new LambdaQueryWrapper<>(ProdValuation.class);
        wrapper.in(ProdValuation::getProductno, productNoList)
                .le(ProdValuation::getValuationdate, endDate)
                .orderByDesc(ProdValuation::getValuationdate);

        // 定义分页查询的参数信息,设置从0开始的第一条数据
        final Page<ProdValuation> page = new Page<>(0, 1);

        // 调用BaseMapper对象的selectPage()函数获取分页后的数据
        final Page<ProdValuation> prodValuationPage = prodValuationMapper.selectPage(page, wrapper);
        if (CollectionUtils.isNotEmpty(prodValuationPage.getRecords())) {
            if (prodValuationPage.getRecords().get(0).getValuationdate() != null) {
                return prodValuationPage.getRecords().get(0).getValuationdate().toLocalDate();
            }
        }
        return null;
    }

必须要确保自定义的Mapper接口继承Mybatis-plus的BaseMapper接口,然后才能调用BaseMapper的分页函数。BaseMapper接口中关于分页的查询函数如下:


/**
 * Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能
 * <p>这个 Mapper 支持 id 泛型</p>
 *
 * @author hubin
 * @since 2016-01-23
 */
public interface BaseMapper<T> extends Mapper<T> {
    /**
     * 根据 Wrapper 条件,查询总记录数
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    Long selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 entity 条件,查询全部记录(并翻页)
     *
     * @param page         分页查询条件
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     * @since 3.5.3.2
     */
    List<T> selectList(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 entity 条件,查询全部记录(并翻页)
     * @param page          分页查询条件
     * @param queryWrapper  实体对象封装操作类(可以为 null)
     * @param resultHandler 结果处理器 {@link ResultHandler}
     * @since 3.5.4
     */
    void selectList(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper, ResultHandler<T> resultHandler);
    /**
     * 根据 Wrapper 条件,查询全部记录(并翻页)
     *
     * @param page         分页查询条件
     * @param queryWrapper 实体对象封装操作类
     * @since 3.5.3.2
     */
    List<Map<String, Object>> selectMaps(IPage<? extends Map<String, Object>> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询全部记录(并翻页)
     *
     * @param page          分页查询条件
     * @param queryWrapper  实体对象封装操作类
     * @param resultHandler 结果处理器 {@link ResultHandler}
     * @since 3.5.4
     */
    void selectMaps(IPage<? extends Map<String, Object>> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper, ResultHandler<Map<String, Object>> resultHandler);

    /**
     * 根据 entity 条件,查询全部记录(并翻页)
     *
     * @param page         分页查询条件(可以为 RowBounds.DEFAULT)
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    <E extends IPage<T>> E selectPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询全部记录(并翻页)
     *
     * @param page         分页查询条件
     * @param queryWrapper 实体对象封装操作类
     */
    <E extends IPage<Map<String, Object>>> E selectMapsPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}

我们知道MyBatis-plus必须要实现BaseMapper接口的分页函数才可以,参考MyBatis源码解析——SqlSession执行Mapper过程,MyBatis中通过MapperProxy类实现动态代理,在Mybatis-plus中,也使用MapperProxy类实现动态代理的。下面是MapperProxy类的关键代码:

public class MybatisMapperProxy<T> implements InvocationHandler, Serializable {
    
    // SQL会话
    private final SqlSession sqlSession;
    // Mapper接口
    private final Class<T> mapperInterface;
    // Mapper接口中的方法缓存,Key为Mapper中的方法,Value为Mapper中方法的调用者
    private final Map<Method, MapperMethodInvoker> methodCache;

    public MybatisMapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            //如果当前是一个Object对象,直接执行其方法
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else {
                //这里会把当前调用的方法封装成一个MapperMethodInvoker然后再调用其invoke方法
                return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    }

    private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
        try {
            return CollectionUtils.computeIfAbsent(methodCache, method, m -> {
                if (m.isDefault()) {
                    try {
                        if (privateLookupInMethod == null) {
                            return new DefaultMethodInvoker(getMethodHandleJava8(method));
                        } else {
                            return new DefaultMethodInvoker(getMethodHandleJava9(method));
                        }
                    } catch (IllegalAccessException | InstantiationException | InvocationTargetException
                        | NoSuchMethodException e) {
                        throw new RuntimeException(e);
                    }
                } else {
                    //正常走这里
                    return new PlainMethodInvoker(new MybatisMapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
                }
            });
        } catch (RuntimeException re) {
            Throwable cause = re.getCause();
            throw cause == null ? re : cause;
        }
    }

在mybitis中MapperMethodInvoker 实例时传的是MapperMethod对象实例,而在mybatis-plus中传的是MybatisMapperMethod实例,两者在逻辑上差异不大,主要是在execute()函数中针对<select>标签增加了分页处理逻辑,源码如下:

    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
            case INSERT: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.insert(command.getName(), param));
                break;
            }
            case UPDATE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.update(command.getName(), param));
                break;
            }
            case DELETE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.delete(command.getName(), param));
                break;
            }
            case SELECT:
                if (method.returnsVoid() && method.hasResultHandler()) {
                    executeWithResultHandler(sqlSession, args);
                    result = null;
                } else if (method.returnsMany()) {
                    result = executeForMany(sqlSession, args);
                } else if (method.returnsMap()) {
                    result = executeForMap(sqlSession, args);
                } else if (method.returnsCursor()) {
                    result = executeForCursor(sqlSession, args);
                } else {
                    // 判断返回值类型是不是IPage或其子类
                    if (IPage.class.isAssignableFrom(method.getReturnType())) {
                        // 执行分页查询
                        result = executeForIPage(sqlSession, args);
                       
                    } else {
                        Object param = method.convertArgsToSqlCommandParam(args);
                        result = sqlSession.selectOne(command.getName(), param);
                        if (method.returnsOptional()
                            && (result == null || !method.getReturnType().equals(result.getClass()))) {
                            result = Optional.ofNullable(result);
                        }
                    }
                }
                break;
            case FLUSH:
                result = sqlSession.flushStatements();
                break;
            default:
                throw new BindingException("Unknown execution method for: " + command.getName());
        }
        if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
            throw new BindingException("Mapper method '" + command.getName()
                + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
        }
        return result;
    }

可以看到,mybatis-plus针对Mapper接口中函数返回值类型为IPage或其子类的查询操作都会执行executeForIPage()函数,executeForIPage函数的代码逻辑如下:

    @SuppressWarnings("all")
    private <E> Object executeForIPage(SqlSession sqlSession, Object[] args) {
        IPage<E> result = null;
        for (Object arg : args) {
            // 查询类型为IPage或其子类的参数
            if (arg instanceof IPage) {
                result = (IPage<E>) arg;
                break;
            }
        }
        Assert.notNull(result, "can't found IPage for args!");

        // 调用MapperMethod.MethodSignature对象的函数convertArgsToSqlCommandParam(),将参数转换为SQL命令的参数
        Object param = method.convertArgsToSqlCommandParam(args);

        // 调用SqlSession的selectList()获取数据
        List<E> list = sqlSession.selectList(command.getName(), param);
        result.setRecords(list);
        return result;
    }

上述代码会调用MapperMethod.MethodSignature对象的函数convertArgsToSqlCommandParam(),将参数转换为SQL命令的参数,然后再执行SqlSession的selectList()函数获取数据,其中convertArgsToSqlCommandParam()函数代码如下:

public static class MethodSignature {
        private final boolean returnsMany;
        private final boolean returnsMap;
        private final boolean returnsVoid;
        private final boolean returnsCursor;
        private final boolean returnsOptional;
        private final Class<?> returnType;
        private final String mapKey;
        private final Integer resultHandlerIndex;
        private final Integer rowBoundsIndex;
        private final ParamNameResolver paramNameResolver;

        public Object convertArgsToSqlCommandParam(Object[] args) {
            return this.paramNameResolver.getNamedParams(args);
        }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值