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

426 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Spring Data JPA 提供了多种形式来操作数据库,旨在简化开发者与数据库的交互。以下是 Spring Data JPA 中操作数据库的主要形式,并附上示例说明。这些形式主要围绕其核心抽象——`Repository`(仓库)接口展开,同时结合不同的查询方式和功能。
### 1. **通过方法名自动生成查询**
这是 Spring Data JPA 最常用的方式,开发者只需在 Repository 接口中定义方法,方法名遵循特定的命名约定,Spring Data JPA 会自动根据方法名生成查询语句。
- **原理**Spring Data JPA 会解析方法名,根据命名规则(如 `findBy`, `existsBy`, `countBy` 等)生成对应的 SQL 或 JPQL 查询。
- **适用场景**:适用于简单的查询需求,例如按字段查找、统计、判断是否存在等。
- **示例**
假设有一个实体类 `User`
```java
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 接口:
```java
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);
}
```
使用方式:
```java
@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 接口:
```java
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);
}
```
使用方式:
```java
@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 会将查询结果映射到这个接口。
- **适用场景**:适用于只需要部分字段的场景,例如列表展示只需要 `id` 和 `name`,而不需要其他字段。
- **示例**
定义投影接口:
```java
package com.example.projection;
public interface UserProjection {
Long getId();
String getName();
}
```
定义 Repository 接口:
```java
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);
}
```
使用方式:
```java
@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 类:
```java
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 接口:
```java
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);
}
```
使用方式:
```java
@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 接口:
```java
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);
}
```
使用方式:
```java
@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 接口:
```java
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> {
}
```
定义动态查询逻辑:
```java
@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 提供了 `CrudRepository` 和 `JpaRepository` 接口,内置了许多常用的 CRUD 方法(如 `save`, `findById`, `delete` 等),可以直接使用。
- **原理**:这些方法由 Spring Data JPA 自动实现,无需开发者额外定义。
- **适用场景**:适用于基本的增删改查操作。
- **示例**
定义 Repository 接口:
```java
package com.example.repository;
import com.example.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
}
```
使用方式:
```java
@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 = true`Spring Data JPA 会将查询作为原生 SQL 执行。
- **适用场景**:适用于需要特定数据库功能或性能优化的复杂查询。
- **示例**
```java
@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!如果有进一步问题,欢迎继续提问。