🎉🎉《Spring Boot实战案例合集》目前已更新181个案例,我们将持续不断的更新。文末有电子书目录。
💪💪永久更新承诺
我们郑重承诺,所有订阅合集的粉丝都将享受永久免费的后续更新服务。
💌💌如何获取
订阅我们的合集《点我订阅》,并通过私信联系我们,我们将第一时间将电子书发送给您。
环境:SpringBoot3.4.2
1. 简介
Spring Data JPA作为Spring Data大家族中的一员,能够轻松实现基于JPA(Java持久化API)的存储库。它简化了构建使用数据访问技术的Spring驱动应用程序的过程。Spring Data JPA旨在通过将所需的工作量减少到实际必要的程度,来显著改善数据访问层的实现。
在使用Spring Data JPA开展持久化操作时,起初我们可能仅需掌握一些基础方法,如save()、findById() 以及delete() ,就能满足日常开发需求。然而,随着项目规模持续拓展、复杂度不断提升,我们逐渐发现,JPA所具备的功能远比想象中丰富得多。若能在合适的场景下精准运用恰当的方法,不仅能为你节省大量调试时间,减少数小时的无效摸索,还能有效降低数据库的负载压力。
本篇文章,我们将为你介绍最实用的 JPA 方法。我会结合实例,保持内容的实用性,并解释何时使用这些方法(以及何时不应使用)。
2.1 save() 和 saveAll()
我们先从简单的开始。
save(entity) → 保存或更新单个实体
saveAll(entities) → 一次性保存多个实体
List<User> users = List.of(new User("Pack", "pack@gmail.com"),new User("Xg", "xg@gmail.com"));userRepository.saveAll(users) ;
与其遍历每个用户并调用 save(),不如使用 saveAll() 批量处理它们。如果要插入数千行数据,可以考虑在 JPA 配置中启用批量插入(如果没有开启,saveAll实际也就是便利的save)。这样可以显著提高性能。
当通过主键获取实体时,这些是首选方法。
Optional<User> user = userRepository.findById(1L);List<User> users = userRepository.findAllById(List.of(1L, 2L, 3L));
findById() 返回的是 Optional,而不是实体本身。这有助于避免 NullPointerException,但别忘了检查值是否存在。
2.3 getReferenceById()
这个方法经常被忽视。与立即查询数据库的 findById() 不同,getReferenceById()返回的是一个延迟引用的代理对象。
User user = userRepository.getReferenceById(1L);
此时,不会触发任何查询。只有当你访问 user 的某个字段时,查询才会执行。何时使用:当你只需要一个用于关联关系的引用(比如设置外键),而不需要完整的实体时,这个方法非常适用。
2.4 existsById()
有没有过那种情况,你只是想看看某条记录在不在数据库里,根本不想把它获取到手里?
boolean exists = userRepository.existsById(1L);
这样可以避免获取不必要的数据,比仅为了检查存在性而调用 findById() 快得多。
2.5 deleteById() 和 deleteAllById()
这是删除实体的直观方法,但许多开发者会忽略一个细微的细节。
userRepository.deleteById(1L);
这些方法会先获取实体,然后再删除它。对于大型数据集来说,这样做效率很低。更聪明的替代方案:如果不需要在删除前加载实体,可以使用带有 DELETE 的自定义 @Query。
("DELETE FROM User u WHERE u.email = :email")void deleteByEmail(("email") String email);
这样可以避免不必要的 SELECT 查询,直接执行 DELETE 语句,提升性能。
2.6 count()
一个简单但强大的方法:
long count = userRepository.count();
这对于仪表盘、监控或快速检查而言十分便利。但需谨慎:对于大型表,它可能会很慢。在实时API中使用它之前一定要三思。
2.7 flush()和saveAndFlush()
大多数开发者并不使用这些方法,但在某些场景下它们可能成为救星。通常情况下,JPA会等到事务提交后才将更改与数据库同步。但使用flush(),你可以强制立即写入更改。
userRepository.saveAndFlush(user);
何时使用:
当你在同一事务中执行其他操作之前,需要确保实体已存在于数据库中时
对于调试事务问题很有用
2.8 findAll(Sort sort) and findAll(Pageable pageable)
对于小型表,使用findAll()获取所有内容并无大碍。但在实际项目中,这会导致性能灾难。相反,应使用排序和分页:
List<User> users = userRepository.findAll(Sort.by("name"));Page<User> page = userRepository.findAll(PageRequest.of(0, 20));
想象这么个场景,你在搭建管理员仪表板的用户列表。总不能把100万条数据一次性全弄到内存里吧,这多伤应用和服务器啊。分页功能这时候就是“救星”,能解决问题。
2.9 @Query 用于自定义方法
有时,派生查询方法(如findByEmail、findByName等)远远不够。这时,@Query就派上用场了。
List<User> searchByEmail( String keyword);
当JPQL(Java持久化查询语言)无法满足需求时,你甚至可以使用原生查询:
(value = "SELECT * FROM users WHERE email LIKE %:keyword%", nativeQuery = true)List<User> searchByEmailNative(("keyword") String keyword);
2.10 @Modifying 用于更新操作
默认情况下,JPA查询是只读的。如果你想在不获取实体的情况下执行更新操作,就需要使用@Modifying。
void updateUserName( Long id, String name);
这避免了加载实体、修改实体然后再保存实体所带来的开销。
2.11 QBE查询
Query by Example (QBE) 是 Spring Data JPA 提供的一种查询方式,它允许你通过创建一个示例对象来构建查询。这个示例对象包含了查询条件,Spring Data JPA 会自动将这些条件转化为 SQL 查询语句。
private final EmpRepository empRepository ;public Optional<Emp> queryEmp1(Integer age) {Emp emp = new Emp() ;emp.setAge(age) ;emp.setName("张三") ;Example<Emp> example = Example.of(emp) ;// Example作为参数。return this.empRepository.findOne(example) ;}
执行上面的方法,控制台输出的SQL
Hibernate: select emp0_.id as id1_0_, emp0_.address as address2_0_, emp0_.age as age3_0_, emp0_.name as name4_0_, emp0_.state as state5_0_ from emp emp0_ where emp0_.age=20 and emp0_.name=? limit ?
SQL的where条件只包含了age与name条件。这是默认行为,会自动将domain对象中的空值忽略,自动将有值的字段添加到查询条件中。这样的查询是不是非常的灵活,要加条件或减条件,只需要前台控制查询参数即可。
2.12 构建动态查询条件
我们可以通过继承 JpaSpecificationExecutor 接口,实现动态查询条件的构建,该接口提供了多个方法,可以用来接收 Specification 参数实现查询条件的动态构建。
import org.springframework.data.jpa.domain.Specification;import static org.springframework.data.jpa.domain.Specification.where;public List<Product> searchProducts(String name, String category, Double minPrice, Double maxPrice) {Specification<Product> spec = where(null);if (name != null && !name.isEmpty()) {spec = spec.and(ProductSpecifications.hasNameLike(name));}if (category != null && !category.isEmpty()) {spec = spec.and(ProductSpecifications.hasCategory(category));}if (minPrice != null && maxPrice != null) {spec = spec.and(ProductSpecifications.isPriceBetween(minPrice, maxPrice));}return productRepository.findAll(spec);}public class ProductSpecifications {public static Specification<Product> hasNameLike(String name) {return (root, query, cb) ->cb.like(root.get("name"), "%" + name + "%");}public static Specification<Product> hasCategory(String category) {return (root, query, cb) ->cb.equal(root.get("category"), category);}public static Specification<Product> isPriceBetween(Double minPrice, Double maxPrice) {return (root, query, cb) ->cb.between(root.get("price"), minPrice, maxPrice);}}
new 的对象也能被 Spring 注入?AspectJ LTW 打破限制!
Spring Boot防御性编程:8种让代码"自愈"的黄金模式
Spring Boot 基于注解实现字段级完整性校验,支持JPA、Mybatis
优化!Controller 接口设计、性能调优等8大黄金法则
企业级实践!Spring Boot 一个注解实现存储加密,支持MyBatis、JPA
零侵入插件!Spring Boot 一键生成 8种格式API接口文档
10 个 Spring Boot 高效编码技巧 + 7个 Tomcat性能调优秘诀
Spring Boot 动态生成签名 URL:私有文件安全访问方案
绝了!Spring Batch 百万数据分区处理,仅需5秒搞定
告别OOM!Spring Boot 流式导出百万数据:支持MyBatis/JPA/Jdbc


