环境
JDK:17
SpringBoot:2.7.5
mybatis-plus-boot-starter:3.5.3.1
pagehelper-spring-boot-starter:1.4.1
1. 问题描述
PageHelper的分页是通过拦截器实现的,通过源码可以看到,PageHelper先是查询了总数,然后查询了数据
之后封装为Page对象,Page继承了ArrayList,包含了总数和数据结果
但因为代码规范的问题,我们的Service方法必须返回Result对象,不能直接返回查询之后的List对象。我们使用 MapStruct 来做对象间的转换,这样在对象转换后就丢失了Page中的总数。
2. 解决方法
思路:增加一个Mybatis拦截器,在PageHelper执行完分页查询之后,将Page对象保存在ThreadLocal中。
1.LocalPage,继承 Closeable,用于保存Page并能够自动清理ThreadLocal
public record LocalPage<T>(Page<T> page) implements Closeable {
public Page<T> getPage() {
return page;
}
public static <T> LocalPage<T> of(Page<T> page) {
return new LocalPage<>(page);
}
@Override
public void close() {
LocalPageHelper.clearLocalPage();
}
}
2.LocalPageHelper,用于封装ThreadLocal操作
public class LocalPageHelper {
private static final ThreadLocal<LocalPage<?>> LOCAL_PAGES = new ThreadLocal<>();
public static void setLocalPage(LocalPage<?> page) {
LOCAL_PAGES.set(page);
}
public static LocalPage<?> getLocalPage() {
return LOCAL_PAGES.get();
}
public static void clearLocalPage() {
LOCAL_PAGES.remove();
}
}
3.LocalPageInterceptor,添加一个拦截器,将LocalPage存入ThreadLocal
@Intercepts({
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
),
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
)
})
public class LocalPageInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
Object result = invocation.proceed();
if (result instanceof Page page) {
LocalPageHelper.setLocalPage(LocalPage.of(page));
}
return result;
}
}
4.在启动类中,禁用 PageHelper 的自动配置
@SpringBootApplication(exclude = PageHelperAutoConfiguration.class)
5.MybatisConfiguration,手动配置插件,可以更好的控制插件顺序
@Configuration
@EnableConfigurationProperties(PageHelperProperties.class)
public class MybatisConfiguration {
private final List<SqlSessionFactory> sqlSessionFactoryList;
private final PageHelperProperties properties;
public MybatisConfiguration(List<SqlSessionFactory> sqlSessionFactoryList, PageHelperProperties properties) {
this.sqlSessionFactoryList = sqlSessionFactoryList;
this.properties = properties;
}
@PostConstruct
public void addMyInterceptor() {
PageInterceptor pageInterceptor = new PageInterceptor();
pageInterceptor.setProperties(this.properties);
LocalPageInterceptor localPageInterceptor = new LocalPageInterceptor();
for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
// 后加入的插件先执行,这里需要先执行我们自定义的LocalPage插件,之后执行分页插件才能拿到Page对象
sqlSessionFactory.getConfiguration().addInterceptor(pageInterceptor);
sqlSessionFactory.getConfiguration().addInterceptor(localPageInterceptor);
}
}
}
6.使用 LocalPage
protected TableDataInfo getDataTable(List<?> list) {
TableDataInfo rspData = new TableDataInfo();
rspData.setCode(HttpStatus.SUCCESS);
rspData.setRows(list);
rspData.setMsg("查询成功");
LocalPage<?> localPage = LocalPageHelper.getLocalPage();
rspData.setTotal(localPage == null ? list.size() : localPage.getPage().getTotal());
return rspData;
}
评论区