强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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
AndfindByNameAndAgeWHERE name=? AND age=?
OrfindByNameOrEmailWHERE name=? OR email=?
BetweenfindByAgeBetweenWHERE age BETWEEN ? AND ?
LessThanfindByAgeLessThanWHERE age < ?
GreaterThanfindByAgeGreaterThanWHERE age > ?
LikefindByNameLikeWHERE name LIKE ?
ContainingfindByNameContainingWHERE name LIKE %?%
StartingWithfindByNameStartingWithWHERE name LIKE ?%
InfindByIdInWHERE id IN (?)
OrderByfindByAgeOrderByNameDescORDER BY name DESC
IsNull / IsNotNullfindByEmailIsNullWHERE email IS NULL
True / FalsefindByActiveTrueWHERE 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);
    }
}

⚠️ 注意事项

  1. N+1 查询问题@OneToMany 默认懒加载,遍历时会触发额外查询,使用 @EntityGraphJOIN FETCH
  2. @Transactional 自调用失效 — 同一个类内部方法调用不会触发事务代理。
  3. save() 不一定执行 INSERT — 存在 ID 时执行 merge(UPDATE)。
  4. 批量操作使用 @Modifying + JPQL — 避免逐条查询更新。

💡 技巧

  1. @EntityGraph 解决 N+1

    @EntityGraph(attributePaths = {"department", "orders"})
    List<User> findByStatus(UserStatus status);
    
  2. Auditing 自动填充

    @EntityListeners(AuditingEntityListener.class)
    public class User {
        @CreatedDate private LocalDateTime createdAt;
        @LastModifiedDate private LocalDateTime updatedAt;
    }
    
  3. Specification 动态查询

    Specification<User> spec = Specification.where(hasName(keyword))
        .and(hasStatus(status))
        .and(createdAfter(startDate));
    userRepository.findAll(spec, pageable);
    

🏢 业务场景

  • CRUD 应用: 快速实现标准数据访问层。
  • 报表查询: 使用 @Query 实现复杂查询。
  • 全文搜索: 结合 Elasticsearch 或数据库全文索引。

📖 扩展阅读