本文涉及到的平台有 https://gitee.com/renrenio/renren-security,https://gitee.com/JavaLionLi/RuoYi-Vue-Plus,探讨的场景主要是需要 join 联查的分页场景,基本上也是这个场景的实现会比较纠结,常见的单表分页,代码生成器中的代码大部分开发平台使用是没有太多问题的
我们这次使用的最新版本
我们以用户的分页查询为例先来看看框架中提供的实现
@RequiresPermissions("sys:user:page")
public Result<PageData<SysUserDTO>> page(@ApiIgnore @RequestParam Map<String, Object> params){
PageData<SysUserDTO> page = sysUserService.page(params);
return new Result<PageData<SysUserDTO>>().ok(page);
}
@ApiModel(value = "响应")
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 编码:0表示成功,其他值表示失败
*/
@ApiModelProperty(value = "编码:0表示成功,其他值表示失败")
private int code = 0;
/**
* 消息内容
*/
@ApiModelProperty(value = "消息内容")
private String msg = "success";
/**
* 响应数据
*/
@ApiModelProperty(value = "响应数据")
private T data;
public Result<T> ok(T data) {
this.setData(data);
return this;
}
public boolean success(){
return code == 0;
}
public Result<T> error() {
this.code = ErrorCode.INTERNAL_SERVER_ERROR;
this.msg = MessageUtils.getMessage(this.code);
return this;
}
public Result<T> error(int code) {
this.code = code;
this.msg = MessageUtils.getMessage(this.code);
return this;
}
public Result<T> error(int code, String msg) {
this.code = code;
this.msg = msg;
return this;
}
public Result<T> error(String msg) {
this.code = ErrorCode.INTERNAL_SERVER_ERROR;
this.msg = msg;
return this;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
@Data
@ApiModel(value = "分页数据")
public class PageData<T> implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "总记录数")
private int total;
@ApiModelProperty(value = "列表数据")
private List<T> list;
/**
* 分页
* @param list 列表数据
* @param total 总记录数
*/
public PageData(List<T> list, long total) {
this.list = list;
this.total = (int)total;
}
}
返回数据后添加了一个 total,方便前端做分页
public PageData<SysUserDTO> page(Map<String, Object> params) {
//转换成like
paramsToLike(params, "username");
//分页
IPage<SysUserEntity> page = getPage(params, Constant.CREATE_DATE, false);
//普通管理员,只能查询所属部门及子部门的数据
UserDetail user = SecurityUser.getUser();
if(user.getSuperAdmin() == SuperAdminEnum.NO.value()) {
params.put("deptIdList", sysDeptService.getSubDeptIdList(user.getDeptId()));
}
//查询
List<SysUserEntity> list = baseDao.getList(params);
return getPageData(list, page.getTotal(), SysUserDTO.class);
}
@Data
@ApiModel(value = "用户管理")
public class SysUserDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "id")
@Null(message="{id.null}", groups = AddGroup.class)
@NotNull(message="{id.require}", groups = UpdateGroup.class)
private Long id;
@ApiModelProperty(value = "用户名", required = true)
@NotBlank(message="{sysuser.username.require}", groups = DefaultGroup.class)
private String username;
@ApiModelProperty(value = "密码")
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@NotBlank(message="{sysuser.password.require}", groups = AddGroup.class)
private String password;
@ApiModelProperty(value = "姓名", required = true)
@NotBlank(message="{sysuser.realname.require}", groups = DefaultGroup.class)
private String realName;
@ApiModelProperty(value = "头像")
private String headUrl;
@ApiModelProperty(value = "性别 0:男 1:女 2:保密", required = true)
@Range(min=0, max=2, message = "{sysuser.gender.range}", groups = DefaultGroup.class)
private Integer gender;
@ApiModelProperty(value = "邮箱")
@Email(message="{sysuser.email.error}", groups = DefaultGroup.class)
private String email;
@ApiModelProperty(value = "手机号")
private String mobile;
@ApiModelProperty(value = "部门ID", required = true)
@NotNull(message="{sysuser.deptId.require}", groups = DefaultGroup.class)
private Long deptId;
@ApiModelProperty(value = "状态 0:停用 1:正常", required = true)
@Range(min=0, max=1, message = "{sysuser.status.range}", groups = DefaultGroup.class)
private Integer status;
@ApiModelProperty(value = "创建时间")
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private Date createDate;
@ApiModelProperty(value = "超级管理员 0:否 1:是")
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private Integer superAdmin;
@ApiModelProperty(value = "角色ID列表")
private List<Long> roleIdList;
@ApiModelProperty(value = "部门名称")
private String deptName;
}
这里的校验用了分组校验,message提示取出的是 common 模块中 resource 中的 validation.properties
sysuser.username.require=\u7528\u6237\u540D\u4E0D\u80FD\u4E3A\u7A7A
sysuser.password.require=\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A
sysuser.realname.require=\u59D3\u540D\u4E0D\u80FD\u4E3A\u7A7A
sysuser.gender.range=\u6027\u522B\u53D6\u503C\u8303\u56F40~2
sysuser.email.require=\u90AE\u7BB1\u4E0D\u80FD\u4E3A\u7A7A
sysuser.email.error=\u90AE\u7BB1\u683C\u5F0F\u4E0D\u6B63\u786E
sysuser.mobile.require=\u624B\u673A\u53F7\u4E0D\u80FD\u4E3A\u7A7A
sysuser.deptId.require=\u90E8\u95E8\u4E0D\u80FD\u4E3A\u7A7A
sysuser.superadmin.range=\u8D85\u7EA7\u7BA1\u7406\u5458\u53D6\u503C\u8303\u56F40~1
sysuser.status.range=\u72B6\u6001\u53D6\u503C\u8303\u56F40~1
sysuser.captcha.require=\u9A8C\u8BC1\u7801\u4E0D\u80FD\u4E3A\u7A7A
sysuser.uuid.require=\u552F\u4E00\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A
这里为啥是 这个码???
后面有机会再详细聊聊校验这块的实现
protected void paramsToLike(Map<String, Object> params, String... likes){
for (String like : likes){
String val = (String)params.get(like);
if (StringUtils.isNotBlank(val)){
params.put(like, "%" + val + "%");
}else {
params.put(like, null);
}
}
}
功能比较简单,其实就是把这个参数 前后拼接 %% , 添加到查询参数
IPage<SysUserEntity> page = getPage(params, Constant.CREATE_DATE, false);
/**
* 获取分页对象
* @param params 分页查询参数
* @param defaultOrderField 默认排序字段
* @param isAsc 排序方式
*/
protected IPage<T> getPage(Map<String, Object> params, String defaultOrderField, boolean isAsc) {
// 1. 构造分页参数
//分页参数
long curPage = 1;
long limit = 10;
// 如果分页参数存在则取出
if(params.get(Constant.PAGE) != null){
curPage = Long.parseLong((String)params.get(Constant.PAGE));
}
if(params.get(Constant.LIMIT) != null){
limit = Long.parseLong((String)params.get(Constant.LIMIT));
}
//分页对象
Page<T> page = new Page<>(curPage, limit);
//分页参数
params.put(Constant.PAGE, page);
// 2. 构造排序参数
//排序字段
String orderField = (String)params.get(Constant.ORDER_FIELD);
String order = (String)params.get(Constant.ORDER);
//前端字段排序
if(StringUtils.isNotBlank(orderField) && StringUtils.isNotBlank(order)){
if(Constant.ASC.equalsIgnoreCase(order)) {
return page.addOrder(OrderItem.asc(orderField));
}else {
return page.addOrder(OrderItem.desc(orderField));
}
}
//没有排序字段,则不排序
if(StringUtils.isBlank(defaultOrderField)){
return page;
}
//默认排序
if(isAsc) {
page.addOrder(OrderItem.asc(defaultOrderField));
}else {
page.addOrder(OrderItem.desc(defaultOrderField));
}
return page;
}
其实主要就做了两件事情
return getPageData(list, page.getTotal(), SysUserDTO.class);
getPageData() 很重要,用来真正构造出分页数据
一共有两个构造方法
protected <T> PageData<T> getPageData(List<?> list, long total, Class<T> target){
List<T> targetList = ConvertUtils.sourceToTarget(list, target);
return new PageData<>(targetList, total);
}
protected <T> PageData<T> getPageData(IPage page, Class<T> target){
return getPageData(page.getRecords(), page.getTotal(), target);
}
public static <T> T sourceToTarget(Object source, Class<T> target){
if(source == null){
return null;
}
T targetObject = null;
try {
targetObject = target.newInstance();
BeanUtils.copyProperties(source, targetObject);
} catch (Exception e) {
logger.error("convert error ", e);
}
return targetObject;
}
public static <T> List<T> sourceToTarget(Collection<?> sourceList, Class<T> target){
if(sourceList == null){
return null;
}
List targetList = new ArrayList<>(sourceList.size());
try {
for(Object source : sourceList){
T targetObject = target.newInstance();
BeanUtils.copyProperties(source, targetObject);
targetList.add(targetObject);
}
}catch (Exception e){
logger.error("convert error ", e);
}
return targetList;
}
<select id="getList" resultType="io.renren.modules.sys.entity.SysUserEntity">
select t1.*, (select t2.name from sys_dept t2 where t2.id=t1.dept_id) deptName from sys_user t1
where t1.super_admin = 0
<if test="username != null and username.trim() != ''">
and t1.username like #{username}
if>
<if test="deptId != null and deptId.trim() != ''">
and t1.dept_id = #{deptId}
if>
<if test="gender != null and gender.trim() != ''">
and t1.gender = #{gender}
if>
<if test="deptIdList != null">
and t1.dept_id in
<foreach item="id" collection="deptIdList" open="(" separator="," close=")">
#{id}
foreach>
if>
select>
这块使用的子查询实现的联查
我们看下对应的
@SaCheckPermission("system:user:list")
@GetMapping("/list")
public TableDataInfo<SysUser> list(SysUser user, PageQuery pageQuery) {
return userService.selectPageUserList(user, pageQuery);
}
这边对应的类是 TableDataInfo
@Override
public TableDataInfo<SysUser> selectPageUserList(SysUser user, PageQuery pageQuery) {
Page<SysUser> page = baseMapper.selectPageUserList(pageQuery.build(), this.buildQueryWrapper(user));
return TableDataInfo.build(page);
}
TableDataInfo
/**
* 表格分页数据对象
*
* @author Lion Li
*/
@Data
@NoArgsConstructor
public class TableDataInfo<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 总记录数
*/
private long total;
/**
* 列表数据
*/
private List<T> rows;
/**
* 消息状态码
*/
private int code;
/**
* 消息内容
*/
private String msg;
/**
* 分页
*
* @param list 列表数据
* @param total 总记录数
*/
public TableDataInfo(List<T> list, long total) {
this.rows = list;
this.total = total;
}
public static <T> TableDataInfo<T> build(IPage<T> page) {
TableDataInfo<T> rspData = new TableDataInfo<>();
rspData.setCode(HttpStatus.HTTP_OK);
rspData.setMsg("查询成功");
rspData.setRows(page.getRecords());
rspData.setTotal(page.getTotal());
return rspData;
}
public static <T> TableDataInfo<T> build(List<T> list) {
TableDataInfo<T> rspData = new TableDataInfo<>();
rspData.setCode(HttpStatus.HTTP_OK);
rspData.setMsg("查询成功");
rspData.setRows(list);
rspData.setTotal(list.size());
return rspData;
}
public static <T> TableDataInfo<T> build() {
TableDataInfo<T> rspData = new TableDataInfo<>();
rspData.setCode(HttpStatus.HTTP_OK);
rspData.setMsg("查询成功");
return rspData;
}
}
可以看出 plus 中对于分页返回参数的封装较深,直接可以通过 Page 构造出,感觉这种方式比较方便啊
看下 mapper 层的写法
@DataPermission({
@DataColumn(key = "deptName", value = "d.dept_id"),
@DataColumn(key = "userName", value = "u.user_id")
})
Page<SysUser> selectPageUserList(@Param("page") Page<SysUser> page, @Param(Constants.WRAPPER) Wrapper<SysUser> queryWrapper);
在dao层做了可以添加数据权限
xml
<select id="selectPageUserList" resultMap="SysUserResult">
select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex,
u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader from
sys_user u
left join sys_dept d on u.dept_id = d.dept_id
${ew.getCustomSqlSegment}
select>
通过 {ew.getCustomSqlSegment} 可以传入 自定义 Wrapper
renren 使用的是先一次性拼接好出来分页结果,使用子查询实现
plus 中使用的是left join,看起来比较常见的方式,mapper 直接拿到 Page