项目实战六-考试模块-科2科3
本节主要讲多表查询
项目初始化
- 项目分析
- 页面如下
-
根据exam_place_course表,通过EasyCode-MybatisCodeHepler生成对应层的类
id create_time name price intro video cover place_id
- place_id是关联exam_palce这张表的id的,因此这张表里面没有省份id、城市id
- 需要通过place_id找到exam_palce对应的id,然后找到plage_region对应的省份、城市id
- 页面如下
- 各个层代码如下
-
po
@Data public class ExamPlaceCourse { /** * 主键 */ private Integer id; private Date createTime; /** * 名称 */ private String name; /** * 价格 */ private Double price; /** * 课程类型:0是课程合集,2是科目2,3是科目3 */ private Short type; private String intro; /** * 视频 */ private String video; /** * 封面 */ private String cover; /** * 考场 */ @ForeignField(ExamPlace.class) private Integer placeId; }
-
query
@EqualsAndHashCode(callSuper = true) @Data public class ExamPlaceCourseQuery extends KeywordQuery { private Short type; private Integer provinceId; private Integer cityId; private Integer placeId; }
-
mapper
public interface ExamPlaceCourseMapper extends BaseMapper<ExamPlaceCourse> {}
-
service
//ExamPlaceCourseService public interface ExamPlaceCourseService extends IService<ExamPlaceCourse>{ void list(ExamPlaceCourseQuery query); } //ExamPlaceCourseServiceImpl @Service @Transactional public class ExamPlaceCourseServiceImpl extends ServiceImpl<ExamPlaceCourseMapper, ExamPlaceCourse> implements ExamPlaceCourseService { @Override public void list(ExamPlaceCourseQuery query) { MpQueryWrapper<ExamPlaceCourse> wrapper = new MpQueryWrapper<>(); //先将数据列表展示,查询代码空着 baseMapper.selectPage(new MpPage<>(query),wrapper).updateQuery(); } }
-
controller
@RestController @RequestMapping("/examPlaceCourses") public class ExamPlaceCourseController extends BaseController<ExamPlaceCourse> { @Autowired private ExamPlaceCourseService service; @Override protected IService<ExamPlaceCourse> getService() { return service; } @GetMapping public R list(ExamPlaceCourseQuery query) { service.list(query); return Rs.ok(query); } }
-
此时页面能正常展示列表
-
实现三级联动
-
搜索条件可以选择省份、城市、考场,属于三级联动,因此需要给客户端提供三级联动的数据,三级联动的后台数据如下
{ "code": 0, "data": [ { "id": 3, "name": "广东", "plate": "粤", "children": [ { "id": 5, "name": "广州", "plate": "A", "children": [ { "id": 1, "name": "白云考场" }, { "id": 5, "name": "天河考场" } ] }, { "id": 12, "name": "深圳", "plate": "B", "children": [ { "id": 4, "name": "保安考场" } ] } ] }, { "id": 10, "name": "广西", "plate": "桂", "children": [ { "id": 11, "name": "南宁", "plate": "A", "children": [ { "id": 3, "name": "电瓶车考场" } ] } ] } ] }
-
从上面数据分析可以得知,需要在CityDto下新增children字段
@Data public class CityDto { private Integer id; private String name; private String plate; private List<?> children; }
-
于是考场信息ExamPlaceController下新增
@GetMapping("/regionExamPlaces") public R listRegionExamPlaces() { return Rs.ok(service.listRegionExamPlaces()); }
-
ExamPlaceService
//ExamPlaceService新增 List<ProvinceDto> listRegionExamPlaces(); //ExamPlaceServiceImpl新增 @Override public List<ProvinceDto> listRegionExamPlaces() { return baseMapper.selectRegionExamPlaces(); }
-
resources.com.zh.jk.mapper下新增ExamPlaceMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zh.jk.mapper.ExamPlaceMapper"> <resultMap id="rmSelectRegionExamPlaces" type="ProvinceDto"> <id column="id" property="id"/> <result column="name" property="name"/> <result column="plate" property="plate"/> <collection property="children" ofType="CityDto"> <id column="city_id" property="id"/> <result column="city_name" property="name"/> <result column="city_plate" property="plate"/> <collection property="children" ofType="ExamPlace"> <id column="exam_place_id" property="id"/> <result column="exam_place_name" property="name"/> </collection> </collection> </resultMap> <select id="selectRegionExamPlaces" resultMap="rmSelectRegionExamPlaces"> SELECT p.id, p.name, p.plate, c.id city_id, c.name city_name, c.plate city_plate, e.id exam_place_id, e.name exam_place_name FROM plate_region p JOIN plate_region c ON c.parent_id = p.id JOIN exam_place e ON e.city_id = c.id AND e.province_id = p.id WHERE p.parent_id = 0 ORDER BY p.pinyin, c.plate </select> </mapper>
-
则可以通过
http://localhost:8888/examPlaces/regionExamPlaces
接口获取三级联动数据
有条件多表查询
注意,本节需要先看下一节的项目重构,然后再继续下面
- 当前列表页面的数据显示的是直接查询,不分页查询的结果,而且是没有条件查询
- 点击编辑,页面如下
- 省份为何为空呢?从数据库表exam_place_course可以知道,该表是没有省份、城市字段的,因此也无法做简单的条件查询,因此当点击列表中的某条数据,是拿不到省份、城市信息的,编辑页面也不能携带到省份、城市信息
- 解决办法
- 改造响应给客户端的数据,通过多表查询,将数据返回全面
- 每条数据除了返回最基本的展示数据外,还要将该考试项目对应的省份、城市、场地,都查询出来,然后返回给客户端,然后点击编辑的时候才能正常展示省份、城市、考场
- 总之就是将多张表的数据查询出来,封装成一条完整的数据
- 改造步骤如下
-
返回数据ExamPlaceCourseVo
//新增省份、城市id @Data public class ExamPlaceCourseVo { /** * 主键 */ private Integer id; /** * 名称 */ private String name; /** * 价格 */ private Double price; /** * 课程类型:0是课程合集,2是科目2,3是科目3 */ private Short type; private String intro; /** * 考场id */ private Integer placeId; private Integer provinceId; private Integer cityId; }
- 改造dao层
- 由于Mybatis-plus只有默认的单表查询方法,因此需要自定义方法与实现
-
ExamPlaceCourseMapper新增自定义方法
public interface ExamPlaceCourseMapper extends BaseMapper<ExamPlaceCourse> { /* 第一个参数page: 会自动在xml的sql语句最后面自动添加LIMIT分页查询条件 第二个参数wapper: 带@Param取别名修饰的参数,是可以传递到xml中去的,然后使用,之前讲过!!! Constants.WRAPPER为ew,即给wrapper参数取的别名为ew Wrapper的类型为Mybatis-plus内部封装的wrapper的最基层类,是LambdaQueryWrapper、QueryWrapper的父类 在xml中可以使用该类的customSqlSegment方法,获取查询条件 ${ew.customSqlSegment} 等价于 WHERE XXXX AND XXX ... * */ MpPage<ExamPlaceCourseVo> selectPageVos(MpPage<ExamPlaceCourseVo> page, @Param(Constants.WRAPPER) Wrapper<ExamPlaceCourseVo> wrapper); }
-
在resources.com.zh.jk.mapper下新建ExamPlaceCourseMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zh.jk.mapper.ExamPlaceCourseMapper"> <resultMap id="rmSelectPageVos" type="ExamPlaceCourseVo"> <id property="id" column="id"/> <result property="name" column="name"/> <result property="price" column="price"/> <result property="type" column="type"/> <result property="placeId" column="place_id"/> <result property="provinceId" column="province_id"/> <result property="cityId" column="city_id"/> </resultMap> <select id="selectPageVos" resultMap="rmSelectPageVos"> SELECT c.id, c.name, c.price, c.type, c.intro, c.place_id, p.province_id, p.city_id FROM exam_place_course c JOIN exam_place p ON p.id = c.place_id ${ew.customSqlSegment} </select> </mapper>
-
改造service的impl层
@Service @Transactional public class ExamPlaceCourseServiceImpl extends ServiceImpl<ExamPlaceCourseMapper, ExamPlaceCourse> implements ExamPlaceCourseService { @Override public PageVo<ExamPlaceCourseVo> list(ExamPlaceCoursePageReqVo query) { MpQueryWrapper<ExamPlaceCourseVo> wrapper = new MpQueryWrapper<>(); Integer placeId = query.getPlaceId(); Integer provinceId = query.getProvinceId(); Integer cityId = query.getCityId(); Short type = query.getType(); // 类型 if (type != null && type >= 0) { wrapper.eq(ExamPlaceCourseVo::getType, type); } // 考场 -> 城市 -> 省份 if (placeId != null && placeId > 0) { wrapper.eq(ExamPlaceCourseVo::getPlaceId, placeId); } else if (cityId != null && cityId > 0) { wrapper.eq(ExamPlaceCourseVo::getCityId, cityId); } else if (provinceId != null && provinceId > 0) { wrapper.eq(ExamPlaceCourseVo::getProvinceId, provinceId); } //关键词搜索 wrapper.like(query.getKeyword(),ExamPlaceCourseVo::getName,ExamPlaceCourseVo:: getIntro); return baseMapper.selectPageVos(new MpPage<>(query),wrapper) .buildVo(); } }
-
运行项目,然后在科2科3模块列表页面点击科目(type)搜索,会崩溃
//引起原因是ew.customSqlSegment方法 Caused by: org.apache.ibatis.builder.BuilderException: Error evaluating expression 'ew.customSqlSegment'. Cause: org.apache.ibatis.ognl.OgnlException: customSqlSegment [com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: can not find lambda cache for this entity [com.zh.jk.pojo.vo.list.ExamPlaceCourseVo]] //引起原因是customSqlSegment方法 Caused by: org.apache.ibatis.ognl.OgnlException: customSqlSegment //引起原因是com.zh.jk.pojo.vo.list.ExamPlaceCourseVo没有lambda cache Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: can not find lambda cache for this entity [com.zh.jk.pojo.vo.list.ExamPlaceCourseVo]
- 本质就是说xml里面的
${ew.customSqlSegment}
方法错误 - 本质原因是ExamPlaceCourseVo类can not find lambda cache
- 本质就是说xml里面的
-
报错原因分析
- ExamPlaceCourseMapper的类继承的BaseMapper
,里面传递的泛型是ExamPlaceCourse类型 -
通过ExamPlaceCourseServiceImpl类中调用的方法是
//本质是调用ExamPlaceCourseMapper的selectPageVos方法 baseMapper.selectPageVos(new MpPage<>(query),wrapper) .buildVo();
- wrapper参数对应的就是ExamPlaceCourseMapper的wrapper,对应xml里面的就是ew这个对象
- ew.customSqlSegment取值本质是通过
BaseMapper<ExamPlaceCourse>
里面的泛型类(ExamPlaceCourse)来取值,而不是通过ExamPlaceCourseVo类来取值,即只有BaseMapper<ExamPlaceCourse>
里面的泛型类才有lambda cache - 那么我们如何才能给ExamPlaceCourseVo也添加lambda cache呢?
- 在MyBatisPlusCfg下改造
- 实现InitializingBean接口
-
实现方法
//只添加一次 @Override public void afterPropertiesSet() throws Exception { /* //解决:can not find lambda cache for this entity报错异常 首先,拥有lambda cache的实体类Entity,才能使用LambdaQueryWrapper<Entity>, 默认情况下,只有BaseMapper<Entity>中的Entity类,才拥有lambda cache, 比如:ExamPlaceCourseMapper继承BaseMapper<ExamPlaceCourse>,ExamPlaceCourse这个类才拥有lambda cache 其他类需要通过TableInfoHelper手动添加lambda cache 由于ExamPlaceCourseVo没有lambda cache,因此需要手动添加 */ MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), ""); TableInfoHelper.initTableInfo(assistant, ExamPlaceCourseVo.class); }
-
重新运行项目,仍然报错
// 通过province_id查询时:Unknown column 'province_id' in 'where clause' // 通过province_id查询时:Unknown column 'city_id' in 'where clause' // 通过name查询时:Column 'name' in where clause is ambiguous
报错原因分析2
- impl中使用的是MpQueryWrapper来查询的,MpQueryWrapper继承自LambdaQueryWrapper
-
LambdaQueryWrapper的查询方式是
wrapper.eq(ExamPlaceCourseVo::getPlaceId, placeId); wrapper.eq(ExamPlaceCourseVo::getCityId, cityId); wrapper.eq(ExamPlaceCourseVo::getProvinceId, provinceId); wrapper.like(query.getKeyword(),ExamPlaceCourseVo::getName,ExamPlaceCourseVo:: getIntro);
-
ExamPlaceCourseVo::getCityId的本质就是“city_id”等价于如下
wrapper.eq("place_id", placeId); wrapper.eq("city_id", cityId); wrapper.eq("province_id", provinceId); wrapper.like(query.getKeyword(),"name",ExamPlaceCourseVo:: getIntro);
-
这些默认的都是在exam_place_course这一张表中去查找,不会到exam_place表中去查找,因此会出现
Unknown column 'province_id' in 'where clause' Unknown column 'city_id' in 'where clause'
-
由于exam_place_course与exam_place都有name字段,不知道具体查询的是哪张表的name因此会出现
Column 'name' in where clause is ambiguous(模棱两可歧义)
- 由于LambdaQueryWrapper只能传参ExamPlaceCourseVo::getCityId格式(city_id),因此不能使用LambdaQueryWrapper的体系类来查询
- 那么能不能传参(”p.city_id”)指定到哪张表中去查找呢? —- QueryWrapper
-
可以使用QueryWrapper,impl的list方法改造如下
QueryWrapper<ExamPlaceCourseVo> wrapper = new QueryWrapper<>(); Integer placeId = query.getPlaceId(); Integer provinceId = query.getProvinceId(); Integer cityId = query.getCityId(); Short type = query.getType(); // 类型 if (type != null && type >= 0) { wrapper.eq("c.type", type); } // 考场 -> 城市 -> 省份 if (placeId != null && placeId > 0) { wrapper.eq("c.place_id", placeId); } else if (cityId != null && cityId > 0) { wrapper.eq("p.city_id", cityId); } else if (provinceId != null && provinceId > 0) { wrapper.eq("p.province_id", provinceId); } // 关键词 String keyword = query.getKeyword(); if (!Strings.isEmpty(keyword)){ wrapper.nested((w)->{ //这里是查表c的name,至于c哪来的,对应xml中的sql语句中 w.like("c.name",keyword).or().like("c.intro",keyword); }); } return baseMapper .selectPageVos(new MpPage<>(query), wrapper) .buildVo();
最终改造
- 由于直接使用QueryWrapper调用方法比较复杂,则可以封装一个子类
- 之前MpQueryWrapper类名修改为MpLambdaQueryWrapper,因为是继承LambdaQueryWrapper
- 同时修改掉所有Impl里面用MpQueryWrapper的修改为MpLambdaQueryWrapper
-
重新建立一个MpQueryWrapper继承自QueryWrapper,并分装好调用方法
//com.zh.jk.common.enhance public class MpQueryWrapper<T> extends QueryWrapper<T> { public final MpQueryWrapper<T> like(Object val, String... columns) { if (val == null) return this; String str = val.toString(); if (str.length() == 0) return this; return (MpQueryWrapper<T>) nested((w) -> { for (String column : columns) { w.like(column, str).or(); } }); } }
-
impl的改造如下
MpQueryWrapper<ExamPlaceCourseVo> wrapper = new MpQueryWrapper<>(); Integer placeId = query.getPlaceId(); Integer provinceId = query.getProvinceId(); Integer cityId = query.getCityId(); Short type = query.getType(); // 类型 if (type != null && type >= 0) { wrapper.eq("c.type", type); } // 考场 -> 城市 -> 省份 if (placeId != null && placeId > 0) { wrapper.eq("c.place_id", placeId); } else if (cityId != null && cityId > 0) { wrapper.eq("p.city_id", cityId); } else if (provinceId != null && provinceId > 0) { wrapper.eq("p.province_id", provinceId); } // 关键词 //这里是查表c的name,至于c哪来的,对应xml中的sql语句中 wrapper.like(query.getKeyword(),"c.name","c.intro"); return baseMapper .selectPageVos(new MpPage<>(query), wrapper) .buildVo();
总结
- MpLambdaQueryWrapper(LambdaQueryWrapper)用来做单表查询的条件工具类,只能在当前表查询
- MpQueryWrapper(QueryWrapper)用来做多表查询的条件工具类,可以自定到其他表查询
- 只有条件查询才会用到wrapper,因此ProvinceVo不需要wapper
- 多表查询不能使用Mybatis-plus自己封装的方法,只能自定义方法
- mapper自定义方法声明
- xml中方法实现