经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring » 查看文章
Spring?Data?Exists查询最佳方法编写示例
来源:jb51  时间:2022/8/1 19:09:34  对本文有异议

简介

在这篇文章中,我将向你展示编写Spring Data Exists查询的最佳方法,从SQL的角度来看,它是高效的。

在做咨询的时候,我遇到了几个常用的选项,而开发者却不知道其实还有更好的选择。

领域模型

让我们假设我们有以下Post 实体。

slug 属性是一个业务键,意味着它有一个唯一的约束,为此,我们可以用下面的注解来注解它 @NaturalIdHibernate注解。

  1. @Entity
  2. @Entity
  3. @Table(
  4. name = "post",
  5. uniqueConstraints = @UniqueConstraint(
  6. name = "UK_POST_SLUG",
  7. columnNames = "slug"
  8. )
  9. )
  10. public class Post {
  11. @Id
  12. private Long id;
  13. private String title;
  14. @NaturalId
  15. private String slug;
  16. public Long getId() {
  17. return id;
  18. }
  19. public Post setId(Long id) {
  20. this.id = id;
  21. return this;
  22. }
  23. public String getTitle() {
  24. return title;
  25. }
  26. public Post setTitle(String title) {
  27. this.title = title;
  28. return this;
  29. }
  30. public Post setSlug(String slug) {
  31. this.slug = slug;
  32. return this;
  33. }
  34. }

如何不使用Spring Data来写Exists查询?

首先,让我们从各种方法开始,这些方法虽然很流行,但你最好避免使用。

用findBy查询模拟存在

Spring Data提供了一种从方法名派生查询的方法,所以你可以写一个findBy 查询来模拟存在,就像这样。

  1. @Repository
  2. public interface PostRepository
  3. extends JpaRepository<Post, Long> {
  4. Optional<Post> findBySlug(String slug);
  5. }

由于findBySlug 方法是用来获取Post 实体的,我见过这样的情况:这个方法被用来进行平等检查,就像下面的例子。

  1. assertTrue(
  2. postRepository.findBySlug(slug).isPresent()
  3. );

这种方法的问题在于,实体的获取实际上只是为了检查是否有一个与所提供的过滤条件相关的记录。

  1. SELECT
  2. p.id AS id1_0_,
  3. p.slug AS slug2_0_,
  4. p.title AS title3_0_
  5. FROM
  6. post p
  7. WHERE
  8. p.slug = 'high-performance-java-persistence'

使用fidnBy 查询来获取实体以检查其存在性是一种资源浪费,因为如果你在slug 属性上有一个索引的话,你不仅不能使用覆盖查询,而且你必须通过网络将实体结果集发送到JDBC驱动程序,只是默默地将其丢弃。

使用实例查询来检查存在性

另一个非常流行的,但效率低下的检查存在性的方法是使用Query By Example功能。

  1. assertTrue(
  2. postRepository.exists(
  3. Example.of(
  4. new Post().setSlug(slug),
  5. ExampleMatcher.matching()
  6. .withIgnorePaths(Post_.ID)
  7. .withMatcher(Post_.SLUG, exact())
  8. )
  9. )
  10. );

Query By Example功能建立了一个Post 实体,在匹配所提供的ExampleMatcher 规范给出的属性时,该实体将被用作参考。

当执行上述Query By Example方法时,Spring Data会生成与之前findBy 方法所生成的相同的SQL查询。

  1. SELECT
  2. p.id AS id1_0_,
  3. p.slug AS slug2_0_,
  4. p.title AS title3_0_
  5. FROM
  6. post p
  7. WHERE
  8. p.slug = 'high-performance-java-persistence'

虽然Query By Example功能对于获取实体可能很有用,但是将其与Spring Data JPA的exists 通用方法Repository ,效率并不高。

如何使用Spring Data编写Exists查询

有更好的方法来编写Spring Data Exists查询。

用existsBy查询方法检查存在性

Spring Data提供了一个existsBy 查询方法,我们可以在PostRepository ,定义如下。

  1. @Repository
  2. public interface PostRepository
  3. extends JpaRepository<Post, Long> {
  4. boolean existsBySlug(String slug);
  5. }

当在PostgreSQL或MySQL上调用existsBySlug 方法时。

  1. assertTrue(
  2. postRepository.existsBySlug(slug)
  3. );

Spring Data会生成以下SQL查询。

  1. SELECT
  2. p.id AS col_0_0_
  3. FROM
  4. post p
  5. WHERE
  6. p.slug = 'high-performance-java-persistence'
  7. LIMIT 1

这个查询的PostgreSQL执行计划看起来如下。

  1. Limit
  2. (cost=0.28..8.29 rows=1 width=8)
  3. (actual time=0.021..0.021 rows=1 loops=1)
  4. -> Index Scan using uk_post_slug on post p
  5. (cost=0.28..8.29 rows=1 width=8)
  6. (actual time=0.020..0.020 rows=1 loops=1)
  7. Index Cond: ((slug)::text = 'high-performance-java-persistence'::text)
  8. Planning Time: 0.088 ms
  9. Execution Time: 0.033 ms

还有,MySQL的,像这样。

  1. -> Limit: 1 row(s)
  2. (cost=0.00 rows=1)
  3. (actual time=0.001..0.001 rows=1 loops=1)
  4. -> Rows fetched before execution
  5. (cost=0.00 rows=1)
  6. (actual time=0.000..0.000 rows=1 loops=1)

所以,这个查询非常快,而且额外的LIMIT 操作并不影响性能,因为它反正是在一个记录的结果集上完成。

用COUNT SQL查询来检查存在性

模拟存在性的另一个选择是使用COUNT查询。

  1. @Repository
  2. public interface PostRepository
  3. extends JpaRepository<Post, Long> {
  4. @Query(value = """
  5. select count(p.id) = 1
  6. from Post p
  7. where p.slug = :slug
  8. """
  9. )
  10. boolean existsBySlugWithCount(@Param("slug") String slug);
  11. }

COUNT 查询在这种特殊情况下可以正常工作,因为我们正在匹配一个UNIQUE列值。

然而,一般来说,对于返回有多条记录的结果集的查询,你应该倾向于使用EXISTS ,而不是COUNT ,正如Lukas Eder在这篇文章中所解释的那样。

在PostgreSQL和MySQL上调用existsBySlugWithCount 方法时。

  1. assertTrue(
  2. postRepository.existsBySlugWithCount(slug)
  3. );

Spring Data会执行以下SQL查询。

  1. SELECT
  2. count(p.id) > 0 AS col_0_0_
  3. FROM
  4. post p
  5. WHERE
  6. p.slug = 'high-performance-java-persistence'

而且,这个查询的PostgreSQL执行计划看起来如下。

  1. Aggregate
  2. (cost=8.29..8.31 rows=1 width=1)
  3. (actual time=0.023..0.024 rows=1 loops=1)
  4. -> Index Scan using uk_post_slug on post p
  5. (cost=0.28..8.29 rows=1 width=8)
  6. (actual time=0.019..0.020 rows=1 loops=1)
  7. Index Cond: ((slug)::text = 'high-performance-java-persistence'::text)
  8. Planning Time: 0.091 ms
  9. Execution Time: 0.044 ms

而在MySQL上。

  1. -> Aggregate: count('1')
  2. (actual time=0.002..0.002 rows=1 loops=1)
  3. -> Rows fetched before execution
  4. (cost=0.00 rows=1)
  5. (actual time=0.000..0.000 rows=1 loops=1)

尽管COUNT操作有一个额外的Aggregate步骤,但由于只有一条记录需要计算,所以这个步骤非常快。

用CASE WHEN EXISTS SQL查询来检查存在性

最后一个模拟存在的选项是使用CASE WHEN EXISTS本地SQL查询。

  1. @Repository
  2. public interface PostRepository
  3. extends JpaRepository<Post, Long> {
  4. @Query(value = """
  5. SELECT
  6. CASE WHEN EXISTS (
  7. SELECT 1
  8. FROM post
  9. WHERE slug = :slug
  10. )
  11. THEN 'true'
  12. ELSE 'false'
  13. END
  14. """,
  15. nativeQuery = true
  16. )
  17. boolean existsBySlugWithCase(@Param("slug") String slug);
  18. }

而且,我们可以像这样调用existsBySlugWithCase 方法。

  1. assertTrue(
  2. postRepository.existsBySlugWithCase(slug)
  3. );

这个查询的PostgreSQL执行计划看起来如下。

  1. Result
  2. (cost=8.29..8.29 rows=1 width=1)
  3. (actual time=0.021..0.022 rows=1 loops=1)
  4. InitPlan 1 (returns $0)
  5. -> Index Only Scan using uk_post_slug on post
  6. (cost=0.27..8.29 rows=1 width=0)
  7. (actual time=0.020..0.020 rows=1 loops=1)
  8. Index Cond: (slug = 'high-performance-java-persistence'::text)
  9. Heap Fetches: 1
  10. Planning Time: 0.097 ms
  11. Execution Time: 0.037 ms

而在MySQL上。

  1. -> Rows fetched before execution
  2. (cost=0.00 rows=1)
  3. (actual time=0.000..0.000 rows=1 loops=1)
  4. -> Select #2 (subquery in projection; run only once)
  5. -> Limit: 1 row(s)
  6. (cost=0.00 rows=1)
  7. (actual time=0.000..0.001 rows=1 loops=1)
  8. -> Rows fetched before execution
  9. (cost=0.00 rows=1)
  10. (actual time=0.000..0.000 rows=1 loops=1)

所以,这和之前的LIMITCOUNT 查询一样快。在其他数据库上,你可能要检查一下,看看是否有什么不同。

结论

因此,如果你想用Spring Data检查一条记录的存在,最简单的方法是使用existsBy 查询方法。

而且,如果查询比较复杂,你不能用Spring Data的查询方法来表达,你可以使用COUNT或CASE WHEN EXISTS查询,因为它们同样快速。

以上就是Spring Data Exists查询最佳方法编写示例的详细内容,更多关于Spring Data Exists查询的资料请关注w3xue其它相关文章!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号