问题描述
我们平时经常需要多表查询,但是多表查询有时也有好多筛选条件,每次都要写sql或者修改sql非常不方便,看到MyBatisPlus集成了查询构造器和分页插件等功能,我们自定义的sql怎么配置也可以享受这种便利呢,下面我以分页多表动态查询为例,在xml中只写基本的全部查询,筛选条件交给Wrapper来实现这种封装。这样可以有很强的扩展性。
场景模拟
假设有两张表菜品表和分类表,而展示菜品时,不但需要菜品信息,还要需要分类表的分类名称
需要用菜品表记录的分类表的category_id来查询分类表的分类名称
sql代码如下:
#分类表
CREATE TABLE `category` (
`id` BIGINT(20) NOT NULL COMMENT '主键',
`type` INT(11) DEFAULT NULL COMMENT '类型 1 菜品分类 2 套餐分类',
`name` VARCHAR(64) COLLATE utf8_bin NOT NULL COMMENT '分类名称',
`sort` INT(11) NOT NULL DEFAULT '0' COMMENT '顺序',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`update_time` DATETIME NOT NULL COMMENT '更新时间',
`create_user` BIGINT(20) NOT NULL COMMENT '创建人',
`update_user` BIGINT(20) NOT NULL COMMENT '修改人',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `idx_category_name` (`name`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='菜品及套餐分类';
#菜品表
CREATE TABLE `dish` (
`id` BIGINT(20) NOT NULL COMMENT '主键',
`name` VARCHAR(64) COLLATE utf8_bin NOT NULL COMMENT '菜品名称',
`category_id` BIGINT(20) NOT NULL COMMENT '菜品分类id',
`price` DECIMAL(10,2) DEFAULT NULL COMMENT '菜品价格',
`code` VARCHAR(64) COLLATE utf8_bin NOT NULL COMMENT '商品码',
`image` VARCHAR(200) COLLATE utf8_bin NOT NULL COMMENT '图片',
`description` VARCHAR(400) COLLATE utf8_bin DEFAULT NULL COMMENT '描述信息',
`status` INT(11) NOT NULL DEFAULT '1' COMMENT '0 停售 1 起售',
`sort` INT(11) NOT NULL DEFAULT '0' COMMENT '顺序',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`update_time` DATETIME NOT NULL COMMENT '更新时间',
`create_user` BIGINT(20) NOT NULL COMMENT '创建人',
`update_user` BIGINT(20) NOT NULL COMMENT '修改人',
`is_deleted` INT(11) NOT NULL DEFAULT '0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `idx_dish_name` (`name`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='菜品管理';
对应的两个表的实体类
/**
* 分类
*/
@Data
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
//类型 1 菜品分类 2 套餐分类
private Integer type;
//分类名称
private String name;
//顺序
private Integer sort;
//创建时间
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
//更新时间
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
//创建人
@TableField(fill = FieldFill.INSERT)
private Long createUser;
//修改人
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
}
/**
菜品
*/
@Data
public class Dish implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
//菜品名称
private String name;
//菜品分类id
private Long categoryId;
//菜品价格
private BigDecimal price;
//商品码
private String code;
//图片
private String image;
//描述信息
private String description;
//0 停售 1 起售
private Integer status;
//顺序
private Integer sort;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT)
private Long createUser;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
//是否删除
private Integer isDeleted;
}
解决方案:
建立一个新的实体类对象,将要返回的属性都写上面,其实就是继承菜品类再扩展一个分类名字
专业的术语称之为dto类,一般将此类型的类专一放在dto包下
@Data public class DishDto extends Dish {//继承了Dish说明属性都继承过来了 //下面的属性名称因为前端的分类的name就是categoryName //所以我这里返回的属性名称也要是categoryName,名字要一一对应的 private String categoryName; } 前端的这个属性的代码 <el-table-column prop="categoryName" label="菜品分类" ></el-table-column>
然后开始分装多表查询
步骤1、在yml文件中配置mapper的xml文件的扫描位置并设置类型别名
#设置自定义sql语句,和mybatis一样,先设置mapper的配置文件即mybatis-plus:mapper-locations:
#这里不设置的话默认位置就是classpath*:/mapper/**/*.xml解释**表示mapper下面可以有目录也可以没有
即 ** 匹配0或者更多的目录
所以这里是默认配置的记住mapper.xml文件扫描位置即可。
然后配置类型别名扫描包!
mybatis-plus:
#配置类型别名所对应的包
#这里如果是多个包名中间加逗号
type-aliases-package: com.ma.reggie.entity,com.ma.reggie.dto
步骤2、先在mapper接口中声明方法,后面和mybatis一样了,mapper接口的代码
@Repository public interface DishMapper extends BaseMapper<Dish> { Page<DishDto> selectPageCD (@Param("page") Page<DishDto> page, @Param(Constants.WRAPPER) Wrapper<DishDto> queryWrapper); }
因为要分页,所以返回对象是Page<DishDto>第一个参数是@Param("page") Page<DishDto> page
因为我们也想使用MyBtaisPlus对应的Wrapper条件构造器,所以我们第二个参数是@Param(Constants.WRAPPER) Wrapper<DishDto> queryWrapper
注意:这个参数对应着后面的xml文件中sql语句末尾都要加 ${ew.customSqlSegment}
这个就是使用条件构造器后后面会动态生成where的语句,所以这里的多表查询一定要用sql99语法,换句话说就是我们不能使用where关键字,不然后面再使用条件构造器会出现两个where!
对应的xml文件
<mapper namespace="com.ma.reggie.mapper.DishMapper"> <resultMap id="dishDtoMap" type="dishDto"> <id column="did" property="id"></id> <result column="name" property="name"></result> <result column="categoryName" property="categoryName"></result> <result column="price" property="price"></result> <result column="status" property="status"></result> <result column="update_time" property="updateTime"></result> <result column="image" property="image"></result> </resultMap> <select id="selectPageCD" resultMap="dishDtoMap"> SELECT d.id did,d.`name` ,c.`name` categoryName,d.`price`,d.`status`,d.`update_time`,d.`image` FROM `dish` d LEFT OUTER JOIN`category` c ON d.`category_id`=c.`id` ${ew.customSqlSegment} </select> </mapper>
最后可以直接在Controller控制层进行自定义的分页
代码如下:
/** * 菜品信息分页查询 * @param page * @param pageSize * @param name * @return */ @GetMapping("/page") public R<Page> page(int page,int pageSize,String name){ Page<DishDto> dishDtoPage = new Page<>(page,pageSize);//创建对应自定义的分页对象 QueryWrapper<DishDto> queryWrapper = new QueryWrapper<>();//使用条件构造器来进行限制筛选条件 queryWrapper.like(name != null,"d.name",name); queryWrapper.orderByDesc("d.`update_time`");//按更新时间排序 dishMapper.selectPageCD(dishDtoPage,queryWrapper);//最后将对应的分页对象和条件构造器都交给自定义的这个分页sql的查询中,然后返回该分页对象即可。 return R.success(dishDtoPage); } }
这里我的个人建议,自定义sql封装Wrapper时,不要使用Lambda类型的条件构造器了,感觉那个过度分装了,它直接利用实体类中的对象直接找对应的sql库中的字段名,简单的还好,一样用,但这种多表查询,比如上面,多表查询中一个是分类表中的name,一个是菜品表中的name,本应该字段名是需要对应的是d.name和c.name的,但是Lambda类型的条件构造器都会最后映射成name,会导致name字段不明确,报错!所以这里直接用QueryWrapper条件构造器,需要啥sql字段就填什么sql字段,比如我这里是要填d.name的,在QueryWrapper条件构造器中直接填d.name还不用想那么多!