当心!Spring Data JPA 中的数据获取误区与最佳实践

科技   2024-10-22 07:30   河北  


当心!Spring Data JPA 中的数据获取误区与最佳实践

你听说过 findAll() 的反模式吗?如果没有,这篇文章非常适合你。即使你听说过,看到几个实际例子也会有所帮助。

findAll() 的反模式指的是从数据库表中获取所有数据,然后对其进行处理,以便只选择必要的数据。直接获取所需的数据不是更好吗?我们将对此进行探讨!

在本文中,我将展示三种从数据库获取数据的方法的性能比较,以突出 findAll() 反模式的糟糕之处。

准备测试场景

为了展示性能比较,我将使用三种使用 Spring Data JPA 获取数据的方法:

  1. 使用 stream() 获取所有数据,并对该流进行操作以获得所需数据。

  2. 使用原生查询获取数据。

  3. 使用 JPQL 查询获取数据。

以上三种方法将应用于三个不同的数据库表:

  • USERS — 包含10列和300行的大表

  • CARS — 包含5列和100行的中等表

  • ANIMALS — 包含3列和5行的小表

这些表之间没有关联,主键为 Long 类型,其余列为 String 或 Boolean。此次比较选择的关系型数据库是 MySQL。

对于每种获取数据的方法,结果将来自100次连续执行的汇总。对于每个数据库表,所有三种获取方法返回相同的数据。现在,我们已经了解了设置,可以进行每个测试用例。

案例 1 — USERS

对于这个案例,我使用了 USERS 表。它在列数和行数上都是最大的。使用的三种方法是:

// stream
public List<Long> getAllUserIdsStream() {
return userRepository.findAll()
.stream()
.filter(User::getActive)
.map(User::getId)
.toList();
}

// native query
@Query(
nativeQuery = true,
value = "select u.id from users where u.active is true")
List<Long> getAllUserIdsNative();

// JPQL query
@Query("select u.id from User u where u.active = true")
List<Long> getAllUserIdsJPQL();

这三种方法都在做同一件事:获取所有活跃用户的 ID。结果如下(时间以毫秒计):

USERS 性能比较(毫秒)— 大型数据库表

使用原生或 JPQL 查询时,相较于 stream() 选项有显著改善。stream() 版本平均耗时14毫秒,而原生和 JPQL 方法的平均时间仅为6毫秒,速度超过两倍。

案例 2 — CARS

第二个测试在中型表上进行,列数和行数较少。我使用了不同大小的数据库表以获得更广泛的比较。

// stream
public List<String> getCarPlatesStream() {
return carRepository.findAll()
.stream()
.map(Car::getPlate)
.toList();
}

// native
@Query(
nativeQuery = true,
value = "select c.plate from cars c")
List<String> getCarPlatesNative();

// JPQL
@Query("select c.id from Car c")
List<String> getCarPlatesJPQL();

这三种方法返回所有汽车的车牌列表。现在,让我们看看结果是否与第一个案例不同。

CARS 性能比较(毫秒)— 中型数据库表

结果与前一个示例相似,stream() 方法表现最差,而其他两种方法明显更好。这次,各方法的平均时间差异较小,因为数据库表也较小。此外,这次只有对流应用了 map() 方法,而前一个场景还进行了 filter(),这增加了时间差异。stream() 方法平均耗时10毫秒,而另外两种方法仅耗时5毫秒。

案例 3 — ANIMALS

在这个案例中,我想检查即使表非常小且操作仅是排序方法,是否仍然存在时间差异。

// stream
public List<Animal> getAnimalsSortedStream() {
return animalRepository.findAll()
.stream()
.sorted(Comparator.comparing(Animal::getColor, String.CASE_INSENSITIVE_ORDER))
.toList();
}

// native
@Query(
nativeQuery = true,
value = "select * from animals order by upper(color)")
List<Animal> getAnimalsSortedNative();

// JPQL
@Query("select a from Animal a order by upper(a.color)")
List<Animal> getAnimalsSortedJPQL();

这三种方法返回按颜色排序的动物列表。结果如下:

ANIMALS 性能比较 — 小型数据库表

在这种情况下,差异较小,但值得注意的是,即使数据库表非常小,使用 JPQL 或原生查询获取数据仍比使用 stream() 方法更有效。在这种情况下,即使是 JPQL(耗时4毫秒)和原生查询(耗时5毫秒)之间的差异也很小。stream() 版本平均耗时7毫秒。

结论

在本文中,我们看到了三种从数据库获取数据的方法,突出了使用 findAll() 方法从数据库表获取所有数据并对其进行处理的低效性,而不是直接获取必要的数据。

三个表(USERS, CARS 和 ANIMALS)的性能比较

如果 findAll() 反模式在上述三个简单表上的表现如此糟糕,想象一下在可能有成千上万行的生产数据库中情况会多糟糕。与其他表的关联可能会使情况更糟。

总之,请务必记住,绝不要在 Spring Data JPA 的 findAll() 方法上执行 stream() 操作,因为这非常低效。


今天就讲到这里,如果有问题需要咨询,大家可以直接留言或扫下方二维码来知识星球找我,我们会尽力为你解答。


AI资源聚合站已经正式上线,该平台不仅仅是一个AI资源聚合站,更是一个为追求知识深度和广度的人们打造的智慧聚集地。通过访问 AI 资源聚合网站 https://ai-ziyuan.techwisdom.cn/,你将进入一个全方位涵盖人工智能和语言模型领域的宝藏库


作者:路条编程(转载请获本公众号授权,并注明作者与出处)

路条编程
路条编程是一个友好的社区,在这里你可以免费学习编程技能,我们旨在激励想学编程的人尝试新的想法和技术,在最短的时间学习到工作中使用到的技术!
 最新文章