SpringDataJpa的使用 – 条件查询、排序查询、分页查询

本文以 Article.java 为数据存放类,操作接口为 ArticleRepository.java

@Data lombok 的注解,用来生成 Getter、Setter、toString、hashCode 方法,当添加该注解的同时在添加 相应方法(如 toString())时,添加的方法不会被覆盖。

@NoArgsConstructor lombok 的注解,用来生成 无参构造函数。

@AllArgsConstructor lombok 的注解,用来生成 全参构造函数。

@JsonIgnore 用来破坏实体类序列化时,产生的无限递归循环。

在本次的测试中,还需要 重写 Author.java 的 toString 方法,目的是这两个类中必须有一方的 toString 方法没有外键属性,可以双方都没有。

Author.java

/**
 * 作者 类
 *
 * @author LJM
 */
@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "AUTHOR")
public class Author {
	/**
	 * 作者 id
	 */
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column(name = "author_id", nullable = false)
	private Long authorId;
	/**
	 * 作者 姓名
	 */
	@Column(name = "author_Name", nullable = false)
	private String authorName;
	/**
	 * 作者 简介
	 */
	@Column(name = "author_referral", nullable = false)
	private String authorReferral;
	/**
	 * 一对多
	 * 一方
	 * (被)维护方
	 *
	 * 文章列表
	 */
	@JsonIgnore
	@OneToMany(mappedBy = "author", fetch = FetchType.EAGER, cascade = {CascadeType.MERGE, CascadeType.REMOVE})
	private List<Article> articleList;
	
	@Override
	public String toString() {
		return "Author{" +
				"authorId=" + authorId +
				", authorName='" + authorName + '\'' +
				", authorReferral='" + authorReferral + '\'' +
				'}';
	}
}

Article.java

/**
 * 文章 类
 *
 * @author LJM
 */
@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "ARTICLE")
public class Article {
	/**
	 * 文章 id
	 */
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column(name = "article_id", nullable = false)
	private Long articleId;
	/**
	 * 文章 标题
	 */
	@Column(name = "article_title", nullable = false)
	private String articleTitle;
	/**
	 * 文章 内容
	 */
	@Column(name = "article_content", nullable = false)
	private String articleContent;
	/**
	 * 文章 类型
	 */
	@Column(name = "article_type", nullable = false)
	private String articleType;
	/**
	 * 文章 阅读量
	 */
	@Column(name = "article_read_number", nullable = false)
	private Integer articleReadNumber;
	/**
	 * 文章 点赞数
	 */
	@Column(name = "article_likes_number", nullable = false)
	private Integer articleLikesNumber;
	/**
	 * 多对一
	 * 多方
	 * 作者外键
	 * 维护方
	 */
	@ManyToOne(fetch = FetchType.EAGER)
	@JoinColumn(name = "article_author_id", nullable = false)
	private Author author;
}

ArticleRepository.java 这只是最基础的内容,会后续添加 接口或方法 来增加功能。

public interface ArticleRepository extends JpaRepository<Article, Long> {
}

排序查询

法 一

使用 JpaRepository 的 List findAll(Sort sort) 方法,无需添加新的 方法或接口。

  • Sort.by(String) 方法的参数是 字符串数组,可以添加一到多个类属性名来排序。

  • ascending 方法表示升序排序(默认就是),写法:Sort.by(String).ascending()。

  • descending 方法表示降序排序,写法:Sort.by(String).descending()。

  • 根据多个类属性来排序时,是在不破坏前一个的排序结果的基础上对后一个进行排序,以此类推。

@Test
public void test1(){
    List<Article> articleList=articleRepository.findAll(Sort.by("articleType").ascending());
    System.out.println("======  升序(类型)  ======");
    articleList.stream().map(Objects::toString).forEach(System.out::println);
    List<Article> articleList1=articleRepository.findAll(Sort.by("articleType","articleReadNumber").ascending());
    System.out.println("======  升序(类型)(阅读量)  ======");
    articleList1.stream().map(Objects::toString).forEach(System.out::println);
}
  • 如果 输入的类属性不存在就会报错,故需要:检验输入的类属性是否存在
@Test
public void test2(){
    List<Article> articleList=articleRepository.findAll(Sort.by("articleType1").ascending());
    System.out.println("======  升序(类型)  ======");
    articleList.stream().map(Objects::toString).forEach(System.out::println);
}
org.springframework.data.mapping.PropertyReferenceException: No property 'articleType1' found for type 'Article'! Did you mean ''articleType''?
  • 能不能设置按第一个属性升序,再按第二个属性降序呢?答案是:当然

by() 是静态方法,不支持变量调用,每一个 by() 后只能添加一个 排序方法。 但是,Sort 有一个 and(Sort) 方法,用来将两个 Sort 合并,该方法由变量调用,理论上可以无限叠加。

@Test
public void test3(){
    List<Article> articleList=articleRepository.findAll(Sort.by("articleType","articleReadNumber").ascending());
    System.out.println("======  升序(类型)(阅读量)  ======");
    articleList.stream().map(Objects::toString).forEach(System.out::println);
    
    Sort sort=Sort.by("articleType").ascending();
    sort=sort.and(Sort.by("articleReadNumber").descending());
    List<Article> articleList1=articleRepository.findAll(sort);
    System.out.println("======  类型升序、再阅读量降序  ======");
    articleList1.stream().map(Objects::toString).forEach(System.out::println);
}

分页查询

法 一

使用 PagingAndSortingRepository 的 Page findAll(Pageable pageable) 方法,JpaRepository 继承了
PagingAndSortingRepository 接口,无需添加新的 方法或接口。

  • Page 几个常用的属性:

    • content 数组 ,存放 List<Object> 的形式的数据。
    • pageable 类,与上面的方法的参数一致,存放了数据的总数、当前页数、当前排序属性 等等。
  • 用 PageRequest.of([页码],[页大小]) 创建 Pageable 变量,然后填入 Page findAll(Pageable) 方法即可。

@Test
public void test4(){
    Pageable pageable=PageRequest.of(0,5);
    Page<Article> articleList=articleRepository.findAll(pageable);
    System.out.println("======  分页结果 0,5  ======");
    articleList.stream().map(Objects::toString).forEach(System.out::println);
    
    Pageable pageable1=PageRequest.of(0,30);
    Page<Article> articleList1=articleRepository.findAll(pageable1);
    System.out.println("======  分页结果 0,30  ======");
    articleList1.stream().map(Objects::toString).forEach(System.out::println);
}
  • 注意:PageRequest.of(0, 5) 表示 第一页,每页5条PageRequest.of(1, 5) 表示 第二页,每页5条

  • 当页数大于数据条数时,如果是第一页,不报错,查询全部;如果不是第一页,那么无法执行,即:当前页码的数据必须大于等于一条,才能成功执行。 我之前测试时,是报错的,现在又可以了。

@Test
public void test5(){
    Pageable pageable=PageRequest.of(1,5);
    Page<Article> articleList=articleRepository.findAll(pageable);
    System.out.println("======  分页结果 0,5  ======");
    articleList.stream().map(Objects::toString).forEach(System.out::println);
    
    Pageable pageable2=PageRequest.of(1,10);
    Page<Article> articleList2=articleRepository.findAll(pageable2);
    System.out.println("======  分页结果 0,30  ======");
    articleList2.stream().map(Objects::toString).forEach(System.out::println);
}

条件查询

法 一
  • 使用 JpaRepository 提供的,通过使用关键字来实现的条件查询。功能丰富。

Spring Data JPA 查询方法支持的关键字

  • 如:根据文章类型查询、根据 文章 类型 和 部分文章内容 查询 等等。
/**
 * 文章 操作 接口
 *
 * @author l'j'm
 */
public interface ArticleRepository extends JpaRepository<Article, Long> {
	/**
	 * 根据 文章 类型 查询
	 *
	 * @param articleType 文章 类型
	 *
	 * @return list
	 */
	List<Article> findByArticleType(String articleType);
	
	/**
	 * 根据 文章 类型 查询
	 *
	 * @param articleType 文章 类型
	 *
	 * @return list
	 */
	List<Article> findAllByArticleType(String articleType);
	
	/**
	 * 根据 文章 类型 和 部分文章内容 查询
	 *
	 * @param articleType    文章 类型
	 * @param articleContent 文章内容
	 *
	 * @return list
	 */
	List<Article> findByArticleTypeAndArticleContentLike(String articleType, String articleContent);
}
  • Jpa 支持的关键字中,已经描述的很详细了,不再重复描述。

  • 标准的写法是以 findBy 开头的,当然 findAllBy 开头的结果是一样,但不建议。

@Test
public void aVoid1(){
    List<Article> articleList=articleRepository.findByArticleType("后端");
    List<Article> articleList1=articleRepository.findAllByArticleType("后端");
    
    System.out.println("======  默认  ======");
    articleList.stream().map(Objects::toString).forEach(System.out::println);
    System.out.println("======  默认(All)  ======");
    articleList1.stream().map(Objects::toString).forEach(System.out::println);
}

@Test
public void aVoid2(){
    List<Article> articleList=articleRepository.findByArticleType("后端");
    List<Article> articleList2=articleRepository
    .findByArticleType("后端",Sort.by("articleReadNumber").ascending());
    List<Article> articleList3=articleRepository
    .findByArticleTypeAndArticleContentLike("后端","%好%");
    
    System.out.println("======  默认  ======");
    articleList.stream().map(Objects::toString).forEach(System.out::println);
    System.out.println("======  根据 文章 类型 查询,升序(阅读量)  ======");
    articleList2.stream().map(Objects::toString).forEach(System.out::println);
    System.out.println("======  根据 文章 类型 和 部分文章内容 查询  ======");
    articleList3.stream().map(Objects::toString).forEach(System.out::println);
}

组合

  • 是指,三种不同的查询分类 两两结合 或 三个成组 地使用,可以适应比较复杂的查询。

条件 + 排序

法 一
  • 在 根据关键字实现的 接口方法中添加参数 Sort 就可以添加排序条件。

  • 注意:方法名不能改,即 想要添加 排序,只需添加添加参数 Sort,不能改方法名。

  • 为什么可以这样写呢?

  • 因为,添加关键字来实现 条件查询,实现时是在内部解析 方法名,然后构建 SQL 语句,最后剩下 findAll(Sort) ,而该方法是有对应的解析方法的。

/**
 * 文章 操作 接口
 *
 * @author l'j'm
 */
public interface ArticleRepository extends JpaRepository<Article, Long> {
	/**
	 * 根据 文章 类型 查询
	 *
	 * @param articleType 文章 类型
	 *
	 * @return list
	 */
	List<Article> findByArticleType(String articleType);
	
	/**
	 * 根据 文章 类型 查询 并排序
	 *
	 * @param articleType 文章 类型
	 * @param sort        排序条件
	 *
	 * @return list
	 */
	List<Article> findByArticleType(String articleType, Sort sort);
	
	/**
	 * 根据 文章 类型 和 部分文章内容 查询
	 *
	 * @param articleType    文章 类型
	 * @param articleContent 文章内容
	 *
	 * @return list
	 */
	List<Article> findByArticleTypeAndArticleContentLike(String articleType, String articleContent);
	
	/**
	 * 根据 文章 类型 和 部分文章内容 查询 并排序
	 *
	 * @param articleType    文章 类型
	 * @param articleContent 文章内容
	 * @param sort           排序条件
	 *
	 * @return list
	 */
	List<Article> findByArticleTypeAndArticleContentLike(String articleType, String articleContent, Sort sort);
}
  • 测试
@Test
public void aVoid2(){
    List<Article> articleList=articleRepository.findByArticleType("后端");
    List<Article> articleList2=articleRepository
    .findByArticleType("后端",Sort.by("articleReadNumber").ascending());
    List<Article> articleList3=articleRepository
    .findByArticleTypeAndArticleContentLike("后端","%好%");
    List<Article> articleList4=articleRepository
    .findByArticleTypeAndArticleContentLike("后端","%好%",Sort.by("articleReadNumber").ascending());
    
    System.out.println("======  默认  ======");
    articleList.stream().map(Objects::toString).forEach(System.out::println);
    System.out.println("======  根据 文章 类型 查询,升序(阅读量)  ======");
    articleList2.stream().map(Objects::toString).forEach(System.out::println);
    System.out.println("======  根据 文章 类型 和 部分文章内容 查询  ======");
    articleList3.stream().map(Objects::toString).forEach(System.out::println);
    System.out.println("======  根据 文章 类型 和 部分文章内容 查询,升序(阅读量)  ======");
    articleList4.stream().map(Objects::toString).forEach(System.out::println);
}

条件 + 分页

法 一
  • 在 根据关键字实现的 接口方法中添加参数 Pageable 就可以添加 分页条件。

  • 注意:方法名不能改,即 想要添加 分页,只需添加添加参数 Pageable,不能改方法名。

  • 为什么可以这样写呢?

  • 因为,添加关键字来实现 条件查询时,实现是在内部解析 方法名,然后构建 SQL 语句,最后剩下 findAll(Pageable) ,而该方法是有对应的解析方法的。

/**
 * 文章 操作 接口
 *
 * @author l'j'm
 */
public interface ArticleRepository extends JpaRepository<Article, Long> {
	/**
	 * 根据 文章 类型 查询
	 *
	 * @param articleType 文章 类型
	 *
	 * @return list
	 */
	List<Article> findByArticleType(String articleType);
	
	/**
	 * 根据 文章 类型 查询 并 分页
	 *
	 * @param articleType 文章 类型
	 * @param pageable    分页条件
	 *
	 * @return list
	 */
	List<Article> findByArticleType(String articleType, Pageable pageable);
	
	/**
	 * 根据 文章 类型 和 部分文章内容 查询
	 *
	 * @param articleType    文章 类型
	 * @param articleContent 文章内容
	 *
	 * @return list
	 */
	List<Article> findByArticleTypeAndArticleContentLike(String articleType, String articleContent);
	
	/**
	 * 根据 文章 类型 和 部分文章内容 查询 并 分页
	 *
	 * @param articleType    文章 类型
	 * @param articleContent 文章内容
	 * @param pageable       分页条件
	 *
	 * @return list
	 */
	List<Article> findByArticleTypeAndArticleContentLike(String articleType, String articleContent, Pageable pageable);
}
  • 测试
@Test
public void aVoid3(){
		List<Article> articleList=articleRepository.findByArticleType("后端");
		List<Article> articleList2=articleRepository
		.findByArticleType("后端",PageRequest.of(0,2));
		List<Article> articleList3=articleRepository
		.findByArticleTypeAndArticleContentLike("后端","%好%");
		List<Article> articleList4=articleRepository
		.findByArticleTypeAndArticleContentLike("后端","%好%",PageRequest.of(1,1));
		
		System.out.println("======  默认  ======");
		articleList.stream().map(Objects::toString).forEach(System.out::println);
		System.out.println("======  根据 文章 类型 查询,分页(0,2)  ======");
		articleList2.stream().map(Objects::toString).forEach(System.out::println);
		System.out.println("======  根据 文章 类型 和 部分文章内容 查询  ======");
		articleList3.stream().map(Objects::toString).forEach(System.out::println);
		System.out.println("======  根据 文章 类型 和 部分文章内容 查询,分页(1,1)  ======");
		articleList4.stream().map(Objects::toString).forEach(System.out::println);
		}

分页 + 排序

法 一
  • Pageable 类中有参数 Sort,只需 将配置好的 Sort 传入 Pageable 中即可

  • 其他的 使用方法,与前文无异。

@Test
public void void1(){
    Sort sort=Sort.by("articleType").ascending();
    List<Article> articleList=articleRepository.findAll(sort);
    System.out.println("======  升序(类型)  ======");
    articleList.stream().map(Objects::toString).forEach(System.out::println);
    
    Pageable pageable=PageRequest.of(1,5);
    Page<Article> articleList1=articleRepository.findAll(pageable);
    System.out.println("======  分页结果 0,5  ======");
    articleList1.stream().map(Objects::toString).forEach(System.out::println);

//	Pageable pageable1 = PageRequest.of(1, 5).withSort(sort);
    Pageable pageable1=PageRequest.of(1,5,sort);
    Page<Article> articleList2=articleRepository.findAll(pageable1);
    System.out.println("======  分页结果 0,5  ======");
    articleList2.stream().map(Objects::toString).forEach(System.out::println);
}

条件 + 分页 + 排序

  • 注意 踩坑:没有 findBy…And…(String…, Pageable, Sort) 这种写法的。

  • 原因:没有 findAll(Pageable, Sort) 这个基方法。

  • findByArticleType(String, Pageable) 和 findByArticleType(String, Sort) 可以执行,是因为有 findAll(Pageable),findAll(Sort)
    这两个基方法。

法 一

看了前文你应该猜到了,没错,就是 用关键字实现条件查询,再添加 带有排序的分页参数实现。

  • 写法 是:findBy…And…(String…, Pageable),直接套用 条件 + 分页 方法即可
/**
 * 文章 操作 接口
 *
 * @author l'j'm
 */
public interface ArticleRepository extends JpaRepository<Article, Long> {
	/**
	 * 根据 文章 类型 查询
	 *
	 * @param articleType 文章 类型
	 *
	 * @return list
	 */
	List<Article> findByArticleType(String articleType);
	
	/**
	 * 根据 文章 类型 查询 并 分页
	 *
	 * @param articleType 文章 类型
	 * @param pageable    分页条件
	 *
	 * @return list
	 */
	List<Article> findByArticleType(String articleType, Pageable pageable);
	
	/**
	 * 根据 文章 类型 和 部分文章内容 查询
	 *
	 * @param articleType    文章 类型
	 * @param articleContent 文章内容
	 *
	 * @return list
	 */
	List<Article> findByArticleTypeAndArticleContentLike(String articleType, String articleContent);
	
	/**
	 * 根据 文章 类型 和 部分文章内容 查询 并 分页
	 *
	 * @param articleType    文章 类型
	 * @param articleContent 文章内容
	 * @param pageable       分页条件
	 *
	 * @return list
	 */
	List<Article> findByArticleTypeAndArticleContentLike(String articleType, String articleContent, Pageable pageable);
}
  • 测试
@Test
public void void2(){
    Sort sort=Sort.by("articleReadNumber").descending();
    Pageable pageable=PageRequest.of(0,5);
    //		Pageable pageable1 = PageRequest.of(0, 5).withSort(sort);
    Pageable pageable1=PageRequest.of(0,5,sort);
    
    List<Article> articleList=articleRepository.findByArticleType("后端",pageable);
    System.out.println("======  后端,分页结果 (0,5)  ======");
    articleList.stream().map(Objects::toString).forEach(System.out::println);
    
    
    List<Article> articleList2=articleRepository.findByArticleType("后端",pageable1);
    System.out.println("======  后端,分页结果 (0,5),排序  ======");
    articleList2.stream().map(Objects::toString).forEach(System.out::println);
}

关于 分组查询 和 统计查询 下篇更新。

(首发)如果对你有帮助,点赞可好!!

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐