Files
notes/resource/java/JPA 操作规范.md
2026-03-01 01:43:46 +08:00

13 KiB
Raw Permalink Blame History

Spring Data JPA 提供了多种形式来操作数据库,旨在简化开发者与数据库的交互。以下是 Spring Data JPA 中操作数据库的主要形式,并附上示例说明。这些形式主要围绕其核心抽象——Repository(仓库)接口展开,同时结合不同的查询方式和功能。

1. 通过方法名自动生成查询

这是 Spring Data JPA 最常用的方式,开发者只需在 Repository 接口中定义方法,方法名遵循特定的命名约定,Spring Data JPA 会自动根据方法名生成查询语句。

  • 原理Spring Data JPA 会解析方法名,根据命名规则(如 findBy, existsBy, countBy 等)生成对应的 SQL 或 JPQL 查询。

  • 适用场景:适用于简单的查询需求,例如按字段查找、统计、判断是否存在等。

  • 示例 假设有一个实体类 User

    package com.example.domain;
    
    import javax.persistence.Entity;
    import javax.persistence.Id;
    
    @Entity
    public class User {
        @Id
        private Long id;
        private String name;
        private Integer age;
    
        // getters and setters
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    }
    

    定义 Repository 接口:

    package com.example.repository;
    
    import com.example.domain.User;
    import org.springframework.data.jpa.repository.JpaRepository;
    
    import java.util.List;
    
    public interface UserRepository extends JpaRepository<User, Long> {
        // 查找名字为指定值的用户
        User findByName(String name);
    
        // 查找年龄大于指定值的用户列表
        List<User> findByAgeGreaterThan(Integer age);
    
        // 判断是否存在指定名字的用户
        boolean existsByName(String name);
    
        // 统计指定年龄的用户数量
        long countByAge(Integer age);
    }
    

    使用方式:

    @Autowired
    private UserRepository userRepository;
    
    public void test() {
        // 查找名字为 "Alice" 的用户
        User user = userRepository.findByName("Alice");
    
        // 查找年龄大于 20 的用户
        List<User> users = userRepository.findByAgeGreaterThan(20);
    
        // 判断是否存在名字为 "Bob" 的用户
        boolean exists = userRepository.existsByName("Bob");
    
        // 统计年龄为 25 的用户数量
        long count = userRepository.countByAge(25);
    }
    

2. 使用 @Query 注解自定义查询

当方法名自动生成的查询无法满足需求时,可以使用 @Query 注解自定义 JPQLJava Persistence Query Language)或原生 SQL 查询。

  • 原理:通过 @Query 注解直接指定查询语句,Spring Data JPA 不会解析方法名,而是使用注解中的查询逻辑。

  • 适用场景:适用于复杂的查询逻辑,例如多表关联、聚合查询、自定义条件等。

  • 示例 定义 Repository 接口:

    package com.example.repository;
    
    import com.example.domain.User;
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.data.jpa.repository.Query;
    import org.springframework.data.repository.query.Param;
    
    import java.util.List;
    
    public interface UserRepository extends JpaRepository<User, Long> {
        // 使用 JPQL 查询名字包含指定关键字的用户
        @Query("SELECT u FROM User u WHERE u.name LIKE %:keyword%")
        List<User> findUsersByNameContaining(@Param("keyword") String keyword);
    
        // 使用原生 SQL 查询
        @Query(value = "SELECT * FROM user WHERE age > :age", nativeQuery = true)
        List<User> findUsersOlderThan(@Param("age") Integer age);
    }
    

    使用方式:

    @Autowired
    private UserRepository userRepository;
    
    public void test() {
        // 查找名字包含 "li" 的用户
        List<User> usersWithKeyword = userRepository.findUsersByNameContaining("li");
    
        // 查找年龄大于 30 的用户(使用原生 SQL)
        List<User> olderUsers = userRepository.findUsersOlderThan(30);
    }
    

3. 使用投影接口(Projection

投影接口用于只查询实体中的部分字段,避免加载不需要的数据,从而优化性能。

  • 原理:定义一个接口,包含需要查询字段的 getter 方法,Spring Data JPA 会将查询结果映射到这个接口。

  • 适用场景:适用于只需要部分字段的场景,例如列表展示只需要 idname,而不需要其他字段。

  • 示例 定义投影接口:

    package com.example.projection;
    
    public interface UserProjection {
        Long getId();
        String getName();
    }
    

    定义 Repository 接口:

    package com.example.repository;
    
    import com.example.domain.User;
    import com.example.projection.UserProjection;
    import org.springframework.data.jpa.repository.JpaRepository;
    
    import java.util.List;
    
    public interface UserRepository extends JpaRepository<User, Long> {
        // 查询所有用户的 id 和 name
        List<UserProjection> findAllProjectedBy();
    
        // 查询指定年龄的用户的 id 和 name
        List<UserProjection> findByAge(Integer age);
    }
    

    使用方式:

    @Autowired
    private UserRepository userRepository;
    
    public void test() {
        // 查询所有用户的 id 和 name
        List<UserProjection> projections = userRepository.findAllProjectedBy();
    
        // 查询年龄为 25 的用户的 id 和 name
        List<UserProjection> ageProjections = userRepository.findByAge(25);
    }
    

4. 使用 DTO 投影(结合 @Query

除了投影接口,也可以在 @Query 中直接映射查询结果到 DTOData Transfer Object)或 VOView Object)类。

  • 原理:在 @Query 中使用 new 关键字直接构造 DTO 对象,将查询结果映射到 DTO 的构造函数。

  • 适用场景:适用于需要返回自定义视图对象,且字段可能来自多表或复杂计算的场景。

  • 示例 定义 DTO 类:

    package com.example.dto;
    
    public class UserDTO {
        private Long id;
        private String name;
    
        public UserDTO(Long id, String name) {
            this.id = id;
            this.name = name;
        }
    
        // getters and setters
    }
    

    定义 Repository 接口:

    package com.example.repository;
    
    import com.example.domain.User;
    import com.example.dto.UserDTO;
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.data.jpa.repository.Query;
    
    import java.util.List;
    
    public interface UserRepository extends JpaRepository<User, Long> {
        @Query("SELECT new com.example.dto.UserDTO(u.id, u.name) FROM User u WHERE u.age > :age")
        List<UserDTO> findUserDTOsByAgeGreaterThan(Integer age);
    }
    

    使用方式:

    @Autowired
    private UserRepository userRepository;
    
    public void test() {
        // 查询年龄大于 20 的用户的 id 和 name,返回 DTO
        List<UserDTO> userDTOs = userRepository.findUserDTOsByAgeGreaterThan(20);
    }
    

5. 使用分页和排序(Paging and Sorting

Spring Data JPA 提供了内置的分页和排序功能,适用于大数据量场景下的分页展示。

  • 原理:通过 Pageable 对象指定分页参数和排序规则,Spring Data JPA 自动处理分页查询和结果封装。

  • 适用场景:适用于列表分页展示、数据量大时分批加载。

  • 示例 定义 Repository 接口:

    package com.example.repository;
    
    import com.example.domain.User;
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.Pageable;
    import org.springframework.data.jpa.repository.JpaRepository;
    
    import java.util.List;
    
    public interface UserRepository extends JpaRepository<User, Long> {
        // 分页查询所有用户
        Page<User> findAll(Pageable pageable);
    
        // 分页查询指定年龄的用户
        Page<User> findByAge(Integer age, Pageable pageable);
    
        // 查询并排序(不分页)
        List<User> findByAgeOrderByNameAsc(Integer age);
    }
    

    使用方式:

    @Autowired
    private UserRepository userRepository;
    
    public void test() {
        // 创建分页参数,第 1 页,每页 10 条,按 name 升序排序
        Pageable pageable = PageRequest.of(0, 10, Sort.by("name").ascending());
    
        // 分页查询所有用户
        Page<User> userPage = userRepository.findAll(pageable);
    
        // 获取查询结果
        List<User> users = userPage.getContent();
        long totalElements = userPage.getTotalElements();
        int totalPages = userPage.getTotalPages();
    
        // 分页查询年龄为 25 的用户
        Page<User> agePage = userRepository.findByAge(25, pageable);
    
        // 查询年龄为 25 的用户并按名字升序排序(不分页)
        List<User> sortedUsers = userRepository.findByAgeOrderByNameAsc(25);
    }
    

6. 使用 Specification 或 Criteria 查询

Spring Data JPA 提供了 JpaSpecificationExecutor 接口,允许开发者动态构建复杂的查询条件(类似 Hibernate 的 Criteria API)。

  • 原理:通过实现 Specification 接口,动态拼接查询条件,适用于运行时根据用户输入构建查询。

  • 适用场景:适用于动态查询条件,例如搜索表单中用户可以选择多个过滤条件。

  • 示例 定义 Repository 接口:

    package com.example.repository;
    
    import com.example.domain.User;
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
    
    public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
    }
    

    定义动态查询逻辑:

    @Autowired
    private UserRepository userRepository;
    
    public List<User> searchUsers(String name, Integer minAge) {
        Specification<User> spec = (root, query, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
    
            if (name != null && !name.isEmpty()) {
                predicates.add(criteriaBuilder.like(root.get("name"), "%" + name + "%"));
            }
            if (minAge != null) {
                predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("age"), minAge));
            }
    
            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
        };
    
        return userRepository.findAll(spec);
    }
    

7. 使用预定义的 CRUD 方法

Spring Data JPA 提供了 CrudRepositoryJpaRepository 接口,内置了许多常用的 CRUD 方法(如 save, findById, delete 等),可以直接使用。

  • 原理:这些方法由 Spring Data JPA 自动实现,无需开发者额外定义。

  • 适用场景:适用于基本的增删改查操作。

  • 示例 定义 Repository 接口:

    package com.example.repository;
    
    import com.example.domain.User;
    import org.springframework.data.jpa.repository.JpaRepository;
    
    public interface UserRepository extends JpaRepository<User, Long> {
    }
    

    使用方式:

    @Autowired
    private UserRepository userRepository;
    
    public void test() {
        // 保存用户
        User user = new User();
        user.setName("Alice");
        user.setAge(25);
        userRepository.save(user);
    
        // 根据 ID 查询用户
        Optional<User> optionalUser = userRepository.findById(1L);
        if (optionalUser.isPresent()) {
            User foundUser = optionalUser.get();
            // 处理 foundUser
        }
    
        // 删除用户
        userRepository.deleteById(1L);
    
        // 查询所有用户
        List<User> allUsers = userRepository.findAll();
    }
    

8. 使用原生 SQL 查询(Native Query

除了 JPQL,还可以通过 @Query 注解使用原生 SQL 查询,直接操作数据库。

  • 原理:设置 nativeQuery = trueSpring Data JPA 会将查询作为原生 SQL 执行。

  • 适用场景:适用于需要特定数据库功能或性能优化的复杂查询。

  • 示例

    @Query(value = "SELECT id, name FROM user WHERE age > :age", nativeQuery = true)
    List<Object[]> findUserDataByAge(@Param("age") Integer age);
    

总结

Spring Data JPA 操作数据库的主要形式包括:

  1. 方法名自动生成查询:通过命名约定生成简单查询。
  2. @Query 注解自定义查询:支持 JPQL 和原生 SQL,适用于复杂查询。
  3. 投影接口(Projection:只查询部分字段,优化性能。
  4. DTO 投影:结合 @Query 将结果映射到自定义 DTO。
  5. 分页和排序:内置分页和排序功能,处理大数据量。
  6. Specification/Criteria 查询:动态构建查询条件,适用于搜索场景。
  7. 预定义 CRUD 方法:直接使用内置的增删改查方法。
  8. 原生 SQL 查询:直接使用数据库特定的 SQL 语句。

这些方式覆盖了从简单到复杂的各种数据库操作需求,开发者可以根据具体场景选择合适的方式。希望这些示例能帮助你更好地理解和应用 Spring Data JPA!如果有进一步问题,欢迎继续提问。