一、需求背景
查询我发起的以及被邀请的工单列表,要求分页查询,排序的具体要求是:
- 先按状态排序,未处理的排前面
 - 再按处理人排序,被邀请的排前面,自己发起的排后面
 - 最后按修改时间倒序
 
处理状态包括三种:
- 0-未处理;
 - 1-已同意;
 - 2-已拒绝
 
邀请表的ddl:
CREATE TABLE `join_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `course_no` varchar(32) NOT NULL COMMENT '课程编号',
  `created_date` datetime DEFAULT current_timestamp() COMMENT '创建时间',
  `creator_id` bigint(20) NOT NULL COMMENT '创建者ID',
  `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除状态:0-正常;1-已删除',
  `modified_date` datetime DEFAULT current_timestamp() COMMENT '更新时间',
  `receive_id` bigint(20) NOT NULL COMMENT '接受邀请人ID',
  `status` smallint(1) NOT NULL COMMENT '状态,0-未处理;1-已同意;2-已拒绝',
  PRIMARY KEY (`id`),
  KEY `IDX_courseNo` (`course_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
- Entity
 
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.util.Date;
/**
 * 邀请记录表
 *
 * @author xxx
 */
@Getter
@NoArgsConstructor
@Table(name = "join_log", indexes = {
        @Index(name = "IDX_courseNo", columnList = "course_no")
})
@Entity
@EntityListeners(AuditingEntityListener.class)
public class JoinLog {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name = "course_no", nullable = false, columnDefinition = "varchar(32) COMMENT '课程编号'")
    private String courseNo;
    @Column(name = "creator_id", nullable = false, columnDefinition = "bigint COMMENT '创建者ID'")
    private long creatorId;
    @Column(name = "receive_id", nullable = false, columnDefinition = "bigint COMMENT '接受邀请人ID'")
    private long receiveId;
    @Column(name = "status", nullable = false, columnDefinition = "smallint(1) COMMENT '状态,0-未处理;1-已同意;2-已拒绝'")
    private JoinLogStatus status;
    @CreatedDate
    @Column(name = "created_date", columnDefinition = "datetime DEFAULT NOW() COMMENT '创建时间'")
    private Date createdDate;
    @LastModifiedDate
    @CreatedDate
    @Column(name = "modified_date", columnDefinition = "datetime DEFAULT NOW() COMMENT '更新时间'")
    private Date modifiedDate;
    @Column(name = "deleted", nullable = false, columnDefinition = "tinyint(1) default 0 COMMENT '逻辑删除状态:0-正常;1-已删除'")
    private LogicDeleteEnum deleted;
}
 
二、目标
手写sql语句,实现数据的排序效果见下:
select jl.*
from join_log jl  where (jl.creator_id = 1192660 or jl.receive_id  = 1192660) and jl.deleted = 0 
order by case when jl.status=0 then 0 else 1 end asc,
 case when jl.creator_id=1192660 then 0 else 1 end desc,  
 jl.modified_date desc 
 limit 0,10;
 

三、分页查询
jpa已很好地支持分页查询,见类JpaSpecificationExecutor.java,不需要你去计算limit 0,10还是limit 10,20,返回的实体也已是分页类Page。当然,你也不需要去额外编写count()求总记录数的sql语句。

 所以,我们的自定义分页查询的出入参仿照着写。
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
Page<T> query(@Param("pageable") Pageable pageable);
 
下面是jpa对分页的具体实现:
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class JoinLogService {
    private final JoinLogRepository joinLogRepository;
    public Page<JoinLog> queryJoinLog(Long userId,
                                      int page,
                                      int size) {
        final Pageable pageable = PageRequest.of(page, size);
        return joinLogRepository.queryByCreatorIdOrReceiveIdOrderByStatus(userId, pageable);
    }
}
 
- 接口JoinLogRepository.java
 
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import java.util.List;
import java.util.Set;
/**
 * @author xxx
 */
public interface JoinLogRepository extends JpaRepository<JoinLog, Long>,
        JpaSpecificationExecutor<JoinLog> {
    /**
     * 分页查询邀请列表.
     *
     * @param userId
     * @param pageable
     * @return
     */
    Page<JoinLog> queryByCreatorIdOrReceiveIdOrderByStatus(Long userId, Pageable pageable);
}
 
- 实现类JpaJoinLogRepository.java
 
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
/**
 * @author xxx
 */
public interface JpaJoinLogRepository extends JoinLogRepository {
    /**
     * 查询我待处理的和发送的工单.
     * <p>
     * 先按状态排序,未处理的排前面
     * 再按处理人排序,非自己的排前面
     * 最后按修改时间倒序
     * </p>
     *
     * @param userId
     * @param pageable
     * @return
     */
    @Override
    @Query("sql语句")
    Page<JoinLog> queryByCreatorIdOrReceiveIdOrderByStatus(@Param("userId") Long userId,
                                                           @Param("pageable") Pageable pageable);
}
 
四、复杂排序
要实现上面的复杂排序,本来只需要把sql语句转换为jql语句。
但是,我们的order by 条件在拼接的时候,不支持小括号。
举例说明:
 最上面的sql语句也可以这么写:
select jl.*
from join_log jl  where (jl.creator_id = 1192660 or jl.receive_id  = 1192660) and jl.deleted = 0 
order by if(jl.status=0, 0, 1) asc,
 if(jl.creator_id=1192660, 0, 1) desc,  
 jl.modified_date desc 
 limit 0,10;
 
但是,在你试图转换为jql的时候,则会报错,变成了下面的错误sql语句。
select jl.*
from join_log jl  where (jl.creator_id = 1192660 or jl.receive_id  = 1192660) and jl.deleted = 0 
order by if() asc,
 if() desc,  
 jl.modified_date desc 
 limit 0,10;
 
所以,我们放弃if语句,换为case when语句。正确的jql语句见下:
@Query("select j from JoinLog j where (j.creatorId=:userId or j.receiveId=:userId) and j.deleted=0 " +
            " order by " +
            " case when j.status=0 then 0 else 1 end asc, " +
            " case when j.creatorId=:userId then 0 else 1 end desc," +
            " j.modifiedDate desc")
 
五、总结
分页和排序,作为查询的基本需求,本文以一个具体的示例,给你演示了从原生sql到hql的过程,最后使用case/when替换if实现了分类排序。














![[极客大挑战 2019]Upload 1](https://img-blog.csdnimg.cn/img_convert/3accf2b23301f02c8446f71b53b1004b.png)




