23 - Spring Data:JPA、Repository、查询方法
23 - Spring Data:JPA、Repository、查询方法
实体映射
import jakarta.persistence.*;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 50)
private String name;
@Column(unique = true, nullable = false)
private String email;
@Enumerated(EnumType.STRING)
private UserStatus status = UserStatus.ACTIVE;
@Column(name = "created_at")
private LocalDateTime createdAt = LocalDateTime.now();
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Order> orders = new ArrayList<>();
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "dept_id")
private Department department;
// getters / setters / constructors
}
public enum UserStatus { ACTIVE, INACTIVE, BANNED }
JPA 注解速查
| 注解 | 说明 |
|---|---|
@Entity | 标记为实体类 |
@Table | 指定表名 |
@Id | 主键 |
@GeneratedValue | 主键生成策略 |
@Column | 列映射 |
@OneToOne | 一对一关联 |
@OneToMany | 一对多关联 |
@ManyToOne | 多对一关联 |
@ManyToMany | 多对多关联 |
@Enumerated | 枚举映射 |
@Embedded / @Embeddable | 嵌入式对象 |
@Transient | 不持久化 |
Repository 接口
import org.springframework.data.jpa.repository.*;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface UserRepository extends JpaRepository<User, Long> {
// 1. 方法名查询(派生查询)
List<User> findByStatus(UserStatus status);
Optional<User> findByEmail(String email);
List<User> findByNameContainingIgnoreCase(String keyword);
List<User> findByCreatedAtBetween(LocalDateTime start, LocalDateTime end);
List<User> findByDepartmentNameAndStatus(String deptName, UserStatus status);
long countByStatus(UserStatus status);
boolean existsByEmail(String email);
// 2. JPQL 查询
@Query("SELECT u FROM User u WHERE u.name LIKE %:keyword% AND u.status = :status")
List<User> searchByKeyword(@Param("keyword") String keyword,
@Param("status") UserStatus status);
// 3. 原生 SQL
@Query(value = "SELECT * FROM users WHERE MATCH(name) AGAINST(:keyword)",
nativeQuery = true)
List<User> fullTextSearch(@Param("keyword") String keyword);
// 4. 更新操作
@Modifying
@Query("UPDATE User u SET u.status = :status WHERE u.id = :id")
int updateStatus(@Param("id") Long id, @Param("status") UserStatus status);
// 5. 分页查询
Page<User> findByStatus(UserStatus status, Pageable pageable);
// 6. 排序
List<User> findByStatusOrderByNameAsc(UserStatus status);
// 7. 限制结果
List<User> findTop5ByStatusOrderByCreatedAtDesc(UserStatus status);
}
方法名查询关键词
| 关键词 | 示例 | SQL |
|---|---|---|
And | findByNameAndAge | WHERE name=? AND age=? |
Or | findByNameOrEmail | WHERE name=? OR email=? |
Between | findByAgeBetween | WHERE age BETWEEN ? AND ? |
LessThan | findByAgeLessThan | WHERE age < ? |
GreaterThan | findByAgeGreaterThan | WHERE age > ? |
Like | findByNameLike | WHERE name LIKE ? |
Containing | findByNameContaining | WHERE name LIKE %?% |
StartingWith | findByNameStartingWith | WHERE name LIKE ?% |
In | findByIdIn | WHERE id IN (?) |
OrderBy | findByAgeOrderByNameDesc | ORDER BY name DESC |
IsNull / IsNotNull | findByEmailIsNull | WHERE email IS NULL |
True / False | findByActiveTrue | WHERE active = true |
分页与排序
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserRepository userRepository;
@GetMapping
public Page<User> list(@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(defaultValue = "id,desc") String sort) {
String[] parts = sort.split(",");
Sort.Direction direction = "asc".equalsIgnoreCase(parts[1])
? Sort.Direction.ASC : Sort.Direction.DESC;
Pageable pageable = PageRequest.of(page, size, Sort.by(direction, parts[0]));
return userRepository.findByStatus(UserStatus.ACTIVE, pageable);
}
}
Service 层
@Service
@Transactional(readOnly = true)
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public Page<User> findAll(Pageable pageable) {
return userRepository.findAll(pageable);
}
public Optional<User> findById(Long id) {
return userRepository.findById(id);
}
@Transactional
public User create(User user) {
if (userRepository.existsByEmail(user.getEmail())) {
throw new BusinessException("DUPLICATE_EMAIL", "邮箱已存在");
}
return userRepository.save(user);
}
@Transactional
public User update(Long id, UserUpdateDTO dto) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("用户不存在"));
user.setName(dto.getName());
user.setEmail(dto.getEmail());
return userRepository.save(user);
}
@Transactional
public void delete(Long id) {
userRepository.deleteById(id);
}
}
⚠️ 注意事项
- N+1 查询问题 —
@OneToMany默认懒加载,遍历时会触发额外查询,使用@EntityGraph或JOIN FETCH。 @Transactional自调用失效 — 同一个类内部方法调用不会触发事务代理。save()不一定执行 INSERT — 存在 ID 时执行 merge(UPDATE)。- 批量操作使用
@Modifying+ JPQL — 避免逐条查询更新。
💡 技巧
@EntityGraph 解决 N+1:
@EntityGraph(attributePaths = {"department", "orders"}) List<User> findByStatus(UserStatus status);Auditing 自动填充:
@EntityListeners(AuditingEntityListener.class) public class User { @CreatedDate private LocalDateTime createdAt; @LastModifiedDate private LocalDateTime updatedAt; }Specification 动态查询:
Specification<User> spec = Specification.where(hasName(keyword)) .and(hasStatus(status)) .and(createdAfter(startDate)); userRepository.findAll(spec, pageable);
🏢 业务场景
- CRUD 应用: 快速实现标准数据访问层。
- 报表查询: 使用
@Query实现复杂查询。 - 全文搜索: 结合 Elasticsearch 或数据库全文索引。