mybatis-plus自动设置records
1、bug描述
以前做分页查询时,我都是直接将page对象扔到sql查询的方法中,mybatis-plus会自动给page进行赋值。然而在这次问题中,返回的total结果中有数据,但是recodes却没有数据

2、bug复现
伪代码如下:
service层代码
1 | public Page<User> queryList() { |
mapper层代码
1 | List<User> queryList( Page<User> page); |
xml查询语句
1 | <select id="queryList" resultType="com.example.entity.User"> |
最后可以看到page对象的数据:

3、定位问题
既然以前是可以在查询sql时给page进行赋值recodes的,那我们就追一下源码,看mybatis-plus是在哪里发起的sql
追源码时,建议将源码下载来,idea中可以直接下,例如:
不然的话,打断点执行时会有偏差,或者运行不到断点
当我们进入到发sql的断点时,点击进入方法内

点几次进入方法内后,我们会进入到com.baomidou.mybatisplus.core.override包下的MybatisMapperMethod#execute方法,这里就是执行SQL的地方

很明显这里分了增删改查,我们的查询,所以这里直接在查询那打断点,直接进入断点

可以看到,这里是根据method.returnsXXX来判断执行的哪个方法,我们分别看一下里面的方法。当看到result = executeForIPage(sqlSession, args);时:
1 | private <E> Object executeForIPage(SqlSession sqlSession, Object[] args) { |
这里面有很明显的setRecords方法,而result其实就是args传参的page对象。所以这大概率就是要找的自动设置records地方了,而其他的方法都没有这个
所以,如何进入这个方法呢?
上面说过,根据method.returnsXXX来判断执行的哪个方法,而从判断来看,可以怀疑这是根据返回值的类型来判断的。这时候的method为:

可以看到reurnType是和查询的sql返回值类型是一样的。因此,要进入executeForIPage的那个方法,只需要将sql的返回值改成IPage对象或者实现类就行了
4、解决问题
将sql的返回对象改Page对象,再次请求,发现recodes被自动赋值进去了
1 | Page<User> queryList( Page<User> page); |

这样问题解决
以前之所以将
sql的返回值设置为List,是为了方便其他查询的复用,那时只需要将page传入null即可,没想到引发了这样的问题。其实这个bug还有其他的解决方法,就是在查询到数据
list后,返回page对象前手动page.setRecords(list)
5、其他拓展
其实之前还好奇过mybatis-plus的total等参数,给sql的分页等是在哪里设置的,这次就一起看看在哪吧。
和上面的语句一样,把sql的返回值改成page对象,进入到MybatisMapperMethod#executeForIPage,断点打到sqlSession.selectList(command.getName(), param)方法上

我们点进sqlSession.selectList方法,发现是一个接口,直接在接口方法打断点,运行时会带我们到对应的实现类上


同样的方法点进this.sqlSessionProxy.selectList(statement, parameter),运行几次后,就到了DefaultSqlSession#selectList方法
然后在executor.query(ms, wrapCollection(parameter), rowBounds, handler)这行打断点,点击进入方法内部

然后就到了Plugin#invoke,可以看到,interceptor就是mybatis-plus的拦截器

打断点进入后,这里就是进行page设置total等,还有分页的地方了

具体的total查询,就在query.willDoQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql)里

可以看到,如果查询结果的false的话,就直接返回空的集合,连原先的查询语句都不执行了。进入willDoQuery,可以看到查询total的语句

回到MybatisPlusInterceptor#intercept,进行分页的地方就在查询total的下面那行代码

该方法会进入PaginationInnerInterceptor#beforeQuery方法,调试下去会发现有使用page.offset()和page.getSize()的地方,就是在这里进行分页

总结下来,其实就是mybatis-plus实现了mybatis的Interceptor拦截器,在里面进行查询total和将sql进行分页





