问题:使用mybatis-plus进行列表分页查询数据时(多表联查),如果查询条件是子表的时候,或者显示的字段是子表的字段,我们应该怎么办?
一、我们先看看常规的查询是怎么实现的,这里的表关系是:一个user表对应多个car表(car的pkUser字段与user的id关联)
1.单表的分页查询(string是前端传的条件,然后我们动态的拼接wrapper。)
注:这里应该是遍历json数据,然后才动态拼接wrapper,这里就略过了
2.多表的分页查询(先查主表然后再查子表)
3.多表的分页查询,子表条件(先根据子表条件查询子表,然后把主表id提取出来,拼接到wrapper中,这样就实现了子表条件动态拼接)
问题:第3种情况虽然可以实现子表条件的动态拼接,但是效率有点低,而且不适用复杂的情况
二、下面我们来看看进阶版的mybatisplus的联表分页查询
引入的依赖如下:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>com.github.yulichang</groupId>
<artifactId>mybatis-plus-join</artifactId>
<version>1.3.11</version>
</dependency>
4.我们先看一下数据库的数据,id为1,2,3的user数据分别对应car表中id为1,2,3,4,5,6,7的数据。下面的代码是mybatis-plus的联表查询和联表分页查询,一个user表对应多个car表
为什么要把两个查询都列出来,因为这里有个坑,我们先看一下联表查询的sql,以及最后返回的数据
这个是正常的联表查询,没有分页效果,因为是一对多的情况,所以sql查到的数据是16条,而不是user表中的12条,但是最后返回的list中是12条,而且一对多的关系也映射进去了,这个查询没有问题,但是联表分页查询时就有问题了
执行selectJoinPage方法后我们发现,查询到的总条数是12条,这个是单查主表数据用来拿到user的总条数,这个没什么问题,问题出现在后边的联表分页查询,经过之前的selectJoinList联表查询我们知道,正常的无条件一对多查询后的数据应该是16条,但是联表分页查询的时候,我们只拿到了10条数据,丢失了6条数据,这就是为什么最后查询到的数据只有6条的原因。
问题出现了我们要怎么解决呢?下面是我推荐的解决方案
注:只有联表分页查询的时候会出现这个情况,正常的单表分页查询是不会出现这个问题的;我们使用这个方法来判断是否出现一对多的情况
boolean resultMap = mpjLambdaWrapper.isResultMap();//是否存在一对多
然后我们执行一下看看效果
我们发现成功查询到了10条主表数据,总条数12条也没问题,关联关系也对上了,问题解决了
5.看到这我们已经基本的解决了联表分页查询主子表条件拼接的问题,以及返回值的主子表关联的问题。但是问题真的解决了么,细心的小伙伴会发现,我们的主题是动态的拼接sql,但是看代码会发现我们里面的实体对象UserExtend和UserExtend.class都是写死的,那么我们怎么实现动态的sql拼接呢?
这个是SFunctiionUtil工具类
package com.kuang.utils.SFunction的工具类;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import java.lang.invoke.CallSite;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/** 最后一次更新时间:2022-12-29 14:40 **/
public class SFunctionUtil {
/** 可序列化 */
private static final int FLAG_SERIALIZABLE = 1;
//mybatisplus的lambdaQueryWrapper在使用eq方法时,只能传入User::getId 不可以传入a->a.getId()
//MyBatisPlus的条件构造器不会真的去调用SFunction这个函数式接口而是只解析实际的方法名.如果解析的是Lambbda表达式,
//那么方法名跟数据库的列名匹配不上就会报错; 如果是方法引用那么方法名通过is/gey/set规则就能找到相应的字段名然后在根据规则转换成数据库表的列名.
/**
* SFunctionUtil.getSFunctionByName(UserExtend.class,"getUserName",String.class) 等同于 UserExtend::getUserName
*
* @param entityClass 对象的class UserExtend.class
* @param name 想要转换的名称 UserExtend::getUserName 中的 getUserName
* @param typeClass userName 字段的类型
* @return
*/
public static <T> SFunction<T,String> getSFunctionByName(Class<T> entityClass,String name,Class typeClass) {
SFunction<T,String> func = null;
final MethodHandles.Lookup lookup = MethodHandles.lookup();
//po的返回Integer的一个方法
MethodType methodType = MethodType.methodType(typeClass, entityClass);
final CallSite site;
try {
//方法名叫做:getSecretLevel 转换为 SFunction function interface对象
site = LambdaMetafactory.altMetafactory(lookup,
"invoke",
MethodType.methodType(SFunction.class),
methodType,
lookup.findVirtual(entityClass, name, MethodType.methodType(typeClass)),
methodType,FLAG_SERIALIZABLE);
func = (SFunction) site.getTarget().invokeExact();
} catch (Throwable e) {
e.printStackTrace();
}
return func;
}
/**
* 把对象中的每个字段的‘方法引用’->UserExtend::getUserName 返回的SFunction放到map中
* @param tClass 查询对象的class
* @param <T>
* @return map.put("id",UserExtend::getId);
* map.put("userName",UserExtend::getUserName);
* @throws ClassNotFoundException
*/
public static <T> Map<String,SFunction<T,String>> getSFunctionMapByClass(Class<T> tClass) {
//1.通过plus中的wrapper获取到对象中的全部字段
LambdaQueryWrapper<T> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.select(tClass,i->true);
String sqlSelect = lambdaQueryWrapper.getSqlSelect();
String[] underlineNameList = sqlSelect.split(",");//下滑线名字集合
//2.组装一个map,map中存放方法名 和 对应字段的数据类型 map.put("userName",String.class);
Map<String,Class> methodNameAndTypeClassMap = new HashMap<>();
//获取所有的bean中所有的成员变量(包括父类)的名称以及类型
List<Field> list = new ArrayList<>();
Class classNew = null;
try{
classNew = Class.forName(tClass.getName());
}catch (Exception e){
e.printStackTrace();
}
while (classNew != null) {
Field[] declaredFields = classNew.getDeclaredFields();
for (Field field : declaredFields) {
if(field.getName().equals("serialVersionUID")){
continue;
}
if (!list.contains(field)) {
list.add(field);
}
}
classNew = (Class<T>) classNew.getSuperclass();
}
//根据字段名分组
Map<String, Field> fieldMap = list.stream().collect(Collectors.toMap(Field::getName, Function.identity(), (a, b) -> a));
for (String underlineName : underlineNameList) {
String camelName = StrUtil.toCamelCase(underlineName);//下滑线转驼峰
Field field = fieldMap.get(camelName);
if(Objects.nonNull(field)){
//获取到对应字段的数据类型class,放到methodNameAndTypeClassMap中
String classTypeName = field.getGenericType().getTypeName();
try{
methodNameAndTypeClassMap.put(camelName,Class.forName(classTypeName));
}catch (Exception e){
e.printStackTrace();
System.out.println(e);
}
}
}
//3.把每个字段都转成 SFunction函数
Map<String,SFunction<T,String>> functionMap = new HashMap<>();
for (Map.Entry<String, Class> entry : methodNameAndTypeClassMap.entrySet()) {
String camelName = entry.getKey();
String methodName = "get" + camelName.substring(0, 1).toUpperCase() + camelName.substring(1);//转成对应的get方法 getUserName
//getSFunctionByName(UserExtend.class,"getUserName",String.class);
SFunction sFunction = getSFunctionByName(tClass, methodName, entry.getValue());
functionMap.put(camelName,sFunction);
}
return functionMap;
}
}
这个是ClassUtilsNioa工具类
package com.kuang.utils.工具类;
import cn.hutool.core.util.StrUtil;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
/**
* 最后一次更新时间:2022-06-08 16:08:31
*/
public class ClassUtilsNioa {
//获取到对象的第index个的字段名称 0为第一个字段
public static <T> String getNameByIndex(T entity, int index) {
return entity.getClass().getDeclaredFields()[index].getName();
}
//为对象中第index个字段赋值
public static <T> void setVByIndex(T entity, int index, String setValue, String format) {
String setName = getNameByIndex(entity, index);
setV(entity, setName, setValue, format);
}
//1.批量对对象中的第index个字段赋值
public static <T> void setVByIndexList(T entity, Map<Integer, String> map, String format) {
for (Integer index : map.keySet()) {
String setName = getNameByIndex(entity, index);
setV(entity, setName, map.get(index), format);
}
}
//2.批量对对象中的第index个字段赋值
public static <T> void setVByIndexList(T entity, int[] indexList, String[] setValueList, String format) {
if (indexList.length != setValueList.length) {
return;
}
for (int i = 0; i < indexList.length; i++) {
String setName = getNameByIndex(entity, indexList[i]);
setV(entity, setName, setValueList[i], format);
}
}
//为对象中的某个名称的字段赋值
public static <T> void setV(T entity, String setName, String setValue) {
setV(entity, setName, setValue, "");
}
//为对象中的某个名称的字段赋值
public static <T> void setV(T entity, String setName, String setValue, String format) {
Map<String, String> map = new HashMap<>();
map.put(setName, setValue);
setV(entity, map, format);
}
//批量为对象中的某个名称的字段赋值
public static <T> void setV(T entity, Map<String, String> map) {
setV(entity, map, "");
}
//批量为对象中的某个名称的字段赋值
public static <T> void setV(T entity, Map<String, String> map, String format) {
if (map == null || map.size() == 0) {
return;
}
List<Field> list = getNameAll(entity);
Map<String, List<Field>> fieldMap = list.stream().collect(Collectors.groupingBy(l -> l.getName()));
Class clazz = entity.getClass();
try {
for (Map.Entry<String, String> entry : map.entrySet()) {
String setName = entry.getKey();
String setValue = entry.getValue();
List<Field> fieldList = fieldMap.get(setName);
if (fieldList != null && fieldList.size() > 0) {
String type = fieldList.get(0).getGenericType().toString();
setName = setName.substring(0, 1).toUpperCase() + setName.substring(1); // 将属性的首字符大写,方便构造get,set方法
if (type.equals("class java.lang.String")) { // 如果type是类类型,则前面包含"class ",后面跟类名
Method m = clazz.getMethod("set" + setName, String.class);
m.invoke(entity, setValue);
} else if (type.equals("class java.util.Date")) {
Method m = clazz.getMethod("set" + setName, Date.class);
Date date = new SimpleDateFormat((format == null || format.length() == 0) ? "yyyy-MM-dd" : format).parse(setValue);
m.invoke(entity, date);
} else if (type.equals("class java.lang.Integer")) {
Method m = entity.getClass().getMethod("set" + setName, Integer.class);
m.invoke(entity, Integer.valueOf(setValue));
} else if (type.equals("class java.lang.Double")) {
Method m = entity.getClass().getMethod("set" + setName, Double.class);
m.invoke(entity, Double.valueOf(setValue));
} else if (type.equals("class java.lang.Long")) {
Method m = entity.getClass().getMethod("set" + setName, Long.class);
m.invoke(entity, Long.valueOf(setValue));
} else if (type.equals("class java.lang.Boolean")) {
Method m = entity.getClass().getMethod("set" + setName, Boolean.class);
m.invoke(entity, Boolean.valueOf(setValue));
} else if (type.equals("class java.math.BigDecimal")) {
Method m = entity.getClass().getMethod("set" + setName, BigDecimal.class);
m.invoke(entity, new BigDecimal(setValue));
} else if (type.equals("int")) {
Method m = entity.getClass().getMethod("set" + setName, int.class);
m.invoke(entity, Integer.valueOf(setValue));
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
//获取到对象中第index个字段值
public static <T> Object getVByIndex(T entity, int index) {
String setName = getNameByIndex(entity, index);
return getV(entity, setName);
}
//获取到对象中的某个名称的字段值
public static <T> Object getV(T entity, String getName) {
try {
//获取到这个名字对应的Field
List<Field> nameAll = getNameAll(entity);
Map<String, List<Field>> map = nameAll.stream().collect(Collectors.groupingBy(n -> n.getName()));
List<Field> fieldList = map.get(getName);
String type = fieldList.get(0).getGenericType().toString();
// 将属性的首字符大写,方便构造get,set方法
getName = getName.substring(0, 1).toUpperCase() + getName.substring(1);
Class<?> clazz = entity.getClass();
//不对基础类型进行get (因为一般的实体类没有int之类的类型,如果有父类中是有int等之类的,默认值是0,所以在get的时候不允许get出int之类的数据)
if (type.equals("class java.lang.String") || type.equals("class java.util.Date") || type.equals("class java.lang.Integer") ||
type.equals("class java.lang.Double") || type.equals("class java.lang.Long") || type.equals("class java.lang.Boolean") ||
type.equals("class java.math.BigDecimal")
) {
getName = getName.substring(0, 1).toUpperCase() + getName.substring(1); // 将属性的首字符大写,方便构造get,set方法
Method m = entity.getClass().getMethod("get" + getName);
Object value = m.invoke(entity);
return value;
}else{
return null;
}
} catch (Exception e) {
throw new RuntimeException("没有get"+getName + "方法");
}
}
//获取到某个对象的所有非空字段值集合
public static <T> List<Object> getVList(T entity) {
return getVList(entity, 0, entity.getClass().getDeclaredFields().length);
}
//获取到某个对象在某个区间的全部非空字段值集合
public static <T> List<Object> getVList(T entity, int begin, int end) {
List<Object> objList = new ArrayList<>();
Field[] fieldList = entity.getClass().getDeclaredFields();
if (end > fieldList.length) {
return null;
}
for (int i = begin; i < end; i++) {
String name = fieldList[i].getName();
Object v = getV(entity, name);
if (!Objects.isNull(v)) {
objList.add(v);
}
}
return objList;
}
//获取到某个对象的全部非空字段值集合(包括父类)
public static <T> List<Object> getVListAndSuper(T entity) throws Exception {
List<Field> list = getNameAll(entity);//全部的字段名称集合
List<Object> objList = new ArrayList<>();//非空字段集合
for (Field field : list) {
String name = field.getName();
name = name.substring(0, 1).toUpperCase() + name.substring(1); // 将属性的首字符大写,方便构造get,set方法
Method m = entity.getClass().getMethod("get" + name);
Object value = m.invoke(entity); // 调用getter方法获取属性值
if (!Objects.isNull(value)) {
objList.add(value);
}
}
return objList;
}
//获取到某个对象的全部字段集合
public static <T> List<Field> getName(T entity) {
Class clazz = entity.getClass();
//获取所有的bean中所有的成员变量(包括父类)的名称以及类型
List<Field> list = new ArrayList<>();
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
if(field.getName().equals("serialVersionUID")){
continue;
}
if (!list.contains(field)) {
list.add(field);
}
}
return list;
}
//获取到某个对象的全部字段集合(包括父类)
public static <T> List<Field> getNameAll(T entity) {
Class clazz = entity.getClass();
//获取所有的bean中所有的成员变量(包括父类)的名称以及类型
List<Field> list = new ArrayList<>();
while (clazz != null) {
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
if(field.getName().equals("serialVersionUID")){
continue;
}
if (!list.contains(field)) {
list.add(field);
}
}
clazz = clazz.getSuperclass();
}
return list;
}
//获取到某个对象的所有非空字段值集合 返回Map
public static <T> Map<String, Object> getVMapList(T entity) {
return getVMapList(entity, 0, entity.getClass().getDeclaredFields().length, true);
}
//获取到某个对象的所有非空字段值集合 返回Map
public static <T> Map<String, Object> getVMapList(T entity, boolean flag) {
return getVMapList(entity, 0, entity.getClass().getDeclaredFields().length, flag);
}
//获取到某个对象在某个区间的全部非空字段值集合 返回Map flag是判断是否转为下划线 true->转化为下划线 false->保持原来的值 默认为true
public static <T> Map<String, Object> getVMapList(T entity, int begin, int end, boolean flag) {
Map<String, Object> map = new HashMap<>();
Field[] fieldList = entity.getClass().getDeclaredFields();
if (end > fieldList.length) {
return null;
}
for (int i = begin; i < end; i++) {
String name = fieldList[i].getName();
if ("serialVersionUID".equals(name)) {
continue;
}
Object v = getV(entity, name);
if (!Objects.isNull(v)) {
if (flag) {
String simpleName = StrUtil.toUnderlineCase(name);//驼峰转下划线
map.put(simpleName, v);
} else {
map.put(name, v);
}
}
}
return map;
}
//获取到某个对象的所有非空字段值集合 返回Map(包括父类) flag:为空是否put 默认否
public static <T> Map<String, Object> getVMapListAndSuper(T entity){
return getVMapListAndSuper(entity, false);
}
//获取到某个对象的所有非空字段值集合 返回Map(包括父类) flag:为空是否put true:是 false:否
public static <T> Map<String, Object> getVMapListAndSuper(T entity, boolean flag){
List<Field> list = getNameAll(entity);//全部的字段Field集合
Map<String, Object> map = new HashMap<>();//
for (Field field : list) {
String name = field.getName();
if ("serialVersionUID".equals(name)) {
continue;
}
Object value = getV(entity, name);
if (Objects.isNull(value)) {
//如果为空判断flag 如果是true则put一个空字符串如果是false则不进行操作
if (flag) {
value = "";
map.put(name, value);
}
} else {
//如果不为空,正常put
map.put(name, value);
}
}
return map;
}
}
下面是完整的代码
//1.首先前台传给我们的json字符串,我们先转换成map形式
//多表分页查询的条件 {"userName":"nioa1","car.name":"黑色"}
Map<String, Object> conditionMap = new HashMap<>();
conditionMap.put("userName","nioa1");
conditionMap.put("car.colour","黑色");
//2.我们在controller层先写好一对多关系的wrapper,这个位置我们是知道具体的class的
Class primaryTableClass = UserExtend.class;//主表的class
MPJLambdaWrapper<UserExtend> mpjLambdaWrapper = new MPJLambdaWrapper<UserExtend>()
.selectAll(UserExtend.class)
.selectCollection(CarExtend.class,UserExtend::getCarExtendList)
.leftJoin(CarExtend.class,CarExtend::getPkUser,UserExtend::getId);
//3.接下来我们需要做的就是动态的拼接sql了,在拼接前我们需要做一下准备操作,把主子表的字段对应的方法引用都提取出来
//也就是说我们要把UserExtend和CarExtend中的字段的方法引用都拿出来,但是问题来了我们怎么知道他的主子表都有谁呢?
//原来mybatis-plus在构建的时候就可以拿到所有的表信息(详细的过程可以去看一下官方的源码,我们这里只需要了解到这个方法可以获取到所有用了plus的实体即可)
//包括但不限于UserExtend.class和CarExtend.class这两个反射类,只要是实现了plus的接口的实体都会在这里面 TableInfoHelper.getTableInfos()
//4.我们先定义一个map缓存,可以在实例化的时候默认执行这个,可以不用每次都手动去转换了,使用的时候只需要用map中的数据就行
/**
* 用来储存每个表中的方法引用
* key: mybatis-plus缓存中的所有表名
* value: 每个表对应实体中的‘方法引用’ UserExtend::getId UserExtend::getUserName
* Map<String, SFunction<T, String> --> map.put("id",UserExtend::getId);
*/
Map<String,Map<String, SFunction<?, String>>> tableSFunctionCache = new ConcurrentHashMap<>();
/**
* 用来储存每个表中的方法引用
* key: mybatis-plus缓存中的所有表class
* value: 每个表对应实体中的‘方法引用’ UserExtend::getId UserExtend::getUserName
* Map<String, SFunction<T, String> --> map.put("id",UserExtend::getId);
*/
Map<Class,Map<String, SFunction<?, String>>> tableClassSFunctionCache = new ConcurrentHashMap<>();
//获取所有实体映射表信息
List<TableInfo> tableInfoList = TableInfoHelper.getTableInfos();
if(CollectionUtil.isEmpty(tableInfoList)){
return;
}
for (TableInfo tableInfo : tableInfoList) {
String tableName = tableInfo.getTableName();//表名称
Class entityType = tableInfo.getEntityType();//实体类型
Map<String, SFunction<?, String>> functionMap = SFunctionUtil.getSFunctionMapByClass(entityType);//
tableSFunctionCache.put(tableName,functionMap);
tableClassSFunctionCache.put(entityType,functionMap);
}
//注:上边的操纵可以理解为 两个map一个是用来存名字的,一个是用来存class的
//Map<String,SFunction<UserExtend,String>> userFunctionMap = new HashMap<>();
//userFunctionMap.put("id",UserExtend::getId);
//userFunctionMap.put("userCode",UserExtend::getUserCode);
//userFunctionMap.put("userName",UserExtend::getUserName);
//Map<String,SFunction<CarExtend,String>> carFunctionMap = new HashMap<>();
//carFunctionMap.put("id",CarExtend::getId);
//carFunctionMap.put("pkUser",CarExtend::getPkUser);
//carFunctionMap.put("name",CarExtend::getName);
//
//Map<String,Map<String, SFunction<?,String>>> classFunctionMap = new HashMap<>();
//classFunctionMap.put("user",userFunctionMap);
//classFunctionMap.put("car",carFunctionMap);
//5.当我们拿到所有表的每个方法引用后,我们就可以去拼接sql了
for (Map.Entry<String, Object> entry : conditionMap.entrySet()) {
String tableName = entry.getKey();//表名
Object value = entry.getValue();//条件
String[] split = tableName.split("\\.");
if(split.length == 1){
//如果长度是1,那么这个字段是主表的
String primaryFieldName = split[0];//主表的字段名
Map<String, SFunction<?, String>> primaryFunctionMap = tableClassSFunctionCache.get(primaryTableClass);
SFunction<?, String> fileSFunction = primaryFunctionMap.get(primaryFieldName);
mpjLambdaWrapper.eq(fileSFunction,value);
}else{
//如果长度是2,那么这个字段是子表的
String sublistTableName = split[0];//子表的表名
String sublistFieldName = split[1];//子表的字段名
Map<String, SFunction<?, String>> tableFunctionMap = tableSFunctionCache.get(sublistTableName);//子表的方法引用
SFunction<?, String> fileSFunction = tableFunctionMap.get(sublistFieldName);
mpjLambdaWrapper.eq(fileSFunction,value);
}
}
//6.好了我们的主子表条件已经动态拼接完成了,接下来就是分页查询了,我们把之前的代码复制过来就行了
//如果出现了一对多的情况
PageImpl page;
if(mpjLambdaWrapper.isResultMap()){
SFunction<?, String> idFunction = tableClassSFunctionCache.get(primaryTableClass).get("id");
//根据主表的查询条件和分页的值,获取到对应主表的id集合
MPJLambdaWrapper wrapper = new MPJLambdaWrapper();
wrapper.select(tableClassSFunctionCache.get(primaryTableClass).get("id"));
//这个代码与上边的区别是没有子表条件了,因为要查询总数是查询的主表数据,没有子表的事,传入条件也只是主表的条件
for (Map.Entry<String, Object> entry : conditionMap.entrySet()) {
String tableName = entry.getKey();//表名
Object value = entry.getValue();//条件
String[] split = tableName.split("\\.");
if(split.length == 1){
//如果长度是1,那么这个字段是主表的
String primaryFieldName = split[0];//主表的字段名
Map<String, SFunction<?, String>> primaryFunctionMap = tableClassSFunctionCache.get(primaryTableClass);
SFunction<?, String> fileSFunction = primaryFunctionMap.get(primaryFieldName);
wrapper.eq(fileSFunction,value);
}
}
IPage ipage = userMapper.selectJoinPage(new Page<>(0, 10),primaryTableClass,wrapper);
long total = ipage.getTotal();//总条数
List records = ipage.getRecords();
List<String> idList = (List) records.stream().map(a -> ClassUtilsNioa.getV(a,"id")).collect(Collectors.toList());
//把id集合拼接到查询条件中,然后不走联表分页查询,只用普通的分页查询即可
mpjLambdaWrapper.in(idFunction,idList);
List userExtendList = userMapper.selectJoinList(primaryTableClass, mpjLambdaWrapper);
page = new PageImpl(userExtendList, PageRequest.of(0, 10), total);
}else{
IPage userExtendIPage = userMapper.selectJoinPage(new Page<>(0, 10), UserExtend.class, mpjLambdaWrapper);
page = new PageImpl(userExtendIPage.getRecords(), PageRequest.of(0, 10), userExtendIPage.getTotal());
}
//这样我们成功的完成了联表分页查询+动态拼接主子表条件啦
System.out.println(JSON.toJSONString(page));
以上就是我们全部的内容了,本文只是提供一种思路,小伙伴们可以根据代码中的工具类去定制自己的联表分页查询啦