Spring Data JPA详解

Spring Data JPA详解:从入门到实战

前言

Spring Data JPA是Spring Data家族中最常用的模块之一,它在JPA规范之上提供了更高层次的抽象,极大地简化了数据访问层的开发。通过Spring Data JPA,开发者只需定义接口,无需编写实现类,即可完成常见的CRUD操作。本文将深入讲解Spring Data JPA的核心概念、使用方法和实战技巧。

一、Spring Data JPA概述

1.1 什么是Spring Data JPA

Spring Data JPA是Spring Data项目的一部分,它简化了基于JPA的数据访问层开发。

scss 复制代码
Spring Data JPA架构
┌─────────────────────────────────────────┐
│           Application Layer            │
│         (Service/Controller)           │
└────────────────┬────────────────────────┘
                 │
┌────────────────▼────────────────────────┐
│         Spring Data JPA                 │
│  ┌───────────────────────────────────┐ │
│  │    Repository Interface           │ │
│  │  - JpaRepository                  │ │
│  │  - CrudRepository                 │ │
│  │  - PagingAndSortingRepository     │ │
│  └───────────────────────────────────┘ │
└────────────────┬────────────────────────┘
                 │
┌────────────────▼────────────────────────┐
│              JPA (Hibernate)            │
└────────────────┬────────────────────────┘
                 │
┌────────────────▼────────────────────────┐
│              Database                   │
└─────────────────────────────────────────┘

1.2 Repository接口体系

scss 复制代码
Repository接口继承关系
┌───────────────────────────────┐
│        Repository<T, ID>      │ ← 标记接口
└───────────────┬───────────────┘
                │
┌───────────────▼───────────────┐
│     CrudRepository<T, ID>     │ ← 基本CRUD
│  - save(), findById()         │
│  - delete(), count()          │
└───────────────┬───────────────┘
                │
┌───────────────▼───────────────┐
│ PagingAndSortingRepository    │ ← 分页排序
│  - findAll(Pageable)          │
│  - findAll(Sort)              │
└───────────────┬───────────────┘
                │
┌───────────────▼───────────────┐
│      JpaRepository<T, ID>     │ ← JPA特有
│  - flush(), saveAndFlush()    │
│  - deleteInBatch()            │
└───────────────────────────────┘

二、快速开始

2.1 Maven依赖

xml 复制代码
<dependencies>
    <!-- Spring Boot Data JPA -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!-- MySQL驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

2.2 配置文件

yaml 复制代码
# application.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/demo?useSSL=false&serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

  jpa:
    hibernate:
      ddl-auto: update  # create/update/validate/none
    show-sql: true
    properties:
      hibernate:
        format_sql: true
        dialect: org.hibernate.dialect.MySQL8Dialect

2.3 实体类定义

java 复制代码
/**
 * 用户实体
 */
@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, length = 50)
    private String username;

    @Column(nullable = false)
    private String password;

    @Column(unique = true)
    private String email;

    private Integer age;

    @Enumerated(EnumType.STRING)
    private UserStatus status;

    @CreationTimestamp
    @Column(updatable = false)
    private LocalDateTime createdAt;

    @UpdateTimestamp
    private LocalDateTime updatedAt;

    // 一对多关系
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<Order> orders;
}

/**
 * 用户状态枚举
 */
public enum UserStatus {
    ACTIVE, INACTIVE, LOCKED
}

/**
 * 订单实体
 */
@Entity
@Table(name = "orders")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String orderNo;

    @Column(precision = 10, scale = 2)
    private BigDecimal amount;

    @Enumerated(EnumType.STRING)
    private OrderStatus status;

    // 多对一关系
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    @CreationTimestamp
    private LocalDateTime createdAt;
}

public enum OrderStatus {
    PENDING, PAID, SHIPPED, COMPLETED, CANCELLED
}

2.4 Repository接口

java 复制代码
/**
 * 用户Repository
 */
public interface UserRepository extends JpaRepository<User, Long> {

    // 基于方法名的查询
    Optional<User> findByUsername(String username);

    Optional<User> findByEmail(String email);

    List<User> findByStatus(UserStatus status);

    List<User> findByAgeBetween(Integer minAge, Integer maxAge);
}

/**
 * 订单Repository
 */
public interface OrderRepository extends JpaRepository<Order, Long> {

    Optional<Order> findByOrderNo(String orderNo);

    List<Order> findByUserId(Long userId);

    List<Order> findByStatus(OrderStatus status);
}

2.5 Service层

java 复制代码
/**
 * 用户服务
 */
@Service
@Transactional(readOnly = true)
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public List<User> findAll() {
        return userRepository.findAll();
    }

    public Optional<User> findById(Long id) {
        return userRepository.findById(id);
    }

    @Transactional
    public User save(User user) {
        return userRepository.save(user);
    }

    @Transactional
    public void deleteById(Long id) {
        userRepository.deleteById(id);
    }
}

三、查询方法

3.1 方法名派生查询

java 复制代码
/**
 * 方法名派生查询示例
 */
public interface UserRepository extends JpaRepository<User, Long> {

    // 等值查询
    User findByUsername(String username);
    List<User> findByStatus(UserStatus status);

    // And/Or组合
    List<User> findByUsernameAndEmail(String username, String email);
    List<User> findByUsernameOrEmail(String username, String email);

    // 比较查询
    List<User> findByAgeGreaterThan(Integer age);
    List<User> findByAgeLessThanEqual(Integer age);
    List<User> findByAgeBetween(Integer min, Integer max);

    // Like查询
    List<User> findByUsernameLike(String pattern);
    List<User> findByUsernameContaining(String keyword);
    List<User> findByUsernameStartingWith(String prefix);
    List<User> findByUsernameEndingWith(String suffix);

    // Null检查
    List<User> findByEmailIsNull();
    List<User> findByEmailIsNotNull();

    // In查询
    List<User> findByStatusIn(Collection<UserStatus> statuses);
    List<User> findByIdIn(List<Long> ids);

    // 排序
    List<User> findByStatusOrderByCreatedAtDesc(UserStatus status);

    // 限制结果
    User findFirstByOrderByCreatedAtDesc();
    List<User> findTop10ByStatus(UserStatus status);

    // 统计
    long countByStatus(UserStatus status);
    boolean existsByUsername(String username);

    // 删除
    void deleteByStatus(UserStatus status);
}

3.2 @Query注解查询

java 复制代码
/**
 * @Query注解查询
 */
public interface UserRepository extends JpaRepository<User, Long> {

    // JPQL查询
    @Query("SELECT u FROM User u WHERE u.email = :email")
    Optional<User> findByEmailJPQL(@Param("email") String email);

    // 原生SQL查询
    @Query(value = "SELECT * FROM users WHERE status = :status", nativeQuery = true)
    List<User> findByStatusNative(@Param("status") String status);

    // 带分页的查询
    @Query("SELECT u FROM User u WHERE u.status = :status")
    Page<User> findByStatusWithPage(@Param("status") UserStatus status, Pageable pageable);

    // 投影查询
    @Query("SELECT u.username, u.email FROM User u WHERE u.id = :id")
    Object[] findUsernameAndEmailById(@Param("id") Long id);

    // 更新操作
    @Modifying
    @Query("UPDATE User u SET u.status = :status WHERE u.id = :id")
    int updateStatusById(@Param("id") Long id, @Param("status") UserStatus status);

    // 删除操作
    @Modifying
    @Query("DELETE FROM User u WHERE u.status = :status")
    int deleteByStatusJPQL(@Param("status") UserStatus status);

    // 复杂条件查询
    @Query("SELECT u FROM User u WHERE " +
           "(:username IS NULL OR u.username LIKE %:username%) AND " +
           "(:status IS NULL OR u.status = :status) AND " +
           "(:minAge IS NULL OR u.age >= :minAge)")
    List<User> searchUsers(@Param("username") String username,
                          @Param("status") UserStatus status,
                          @Param("minAge") Integer minAge);

    // 关联查询
    @Query("SELECT u FROM User u LEFT JOIN FETCH u.orders WHERE u.id = :id")
    Optional<User> findByIdWithOrders(@Param("id") Long id);

    // 统计查询
    @Query("SELECT u.status, COUNT(u) FROM User u GROUP BY u.status")
    List<Object[]> countByStatusGroup();
}

3.3 Specification动态查询

java 复制代码
/**
 * 启用JpaSpecificationExecutor
 */
public interface UserRepository extends JpaRepository<User, Long>,
                                       JpaSpecificationExecutor<User> {
}

/**
 * 用户查询条件
 */
@Data
public class UserSearchCriteria {
    private String username;
    private String email;
    private UserStatus status;
    private Integer minAge;
    private Integer maxAge;
    private LocalDateTime startDate;
    private LocalDateTime endDate;
}

/**
 * 用户Specification构建器
 */
public class UserSpecifications {

    public static Specification<User> withCriteria(UserSearchCriteria criteria) {
        return (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();

            // 用户名模糊查询
            if (StringUtils.hasText(criteria.getUsername())) {
                predicates.add(cb.like(root.get("username"),
                    "%" + criteria.getUsername() + "%"));
            }

            // 邮箱精确查询
            if (StringUtils.hasText(criteria.getEmail())) {
                predicates.add(cb.equal(root.get("email"), criteria.getEmail()));
            }

            // 状态查询
            if (criteria.getStatus() != null) {
                predicates.add(cb.equal(root.get("status"), criteria.getStatus()));
            }

            // 年龄范围
            if (criteria.getMinAge() != null) {
                predicates.add(cb.greaterThanOrEqualTo(root.get("age"), criteria.getMinAge()));
            }
            if (criteria.getMaxAge() != null) {
                predicates.add(cb.lessThanOrEqualTo(root.get("age"), criteria.getMaxAge()));
            }

            // 日期范围
            if (criteria.getStartDate() != null) {
                predicates.add(cb.greaterThanOrEqualTo(root.get("createdAt"),
                    criteria.getStartDate()));
            }
            if (criteria.getEndDate() != null) {
                predicates.add(cb.lessThanOrEqualTo(root.get("createdAt"),
                    criteria.getEndDate()));
            }

            return cb.and(predicates.toArray(new Predicate[0]));
        };
    }

    // 组合Specification
    public static Specification<User> hasStatus(UserStatus status) {
        return (root, query, cb) -> cb.equal(root.get("status"), status);
    }

    public static Specification<User> usernameLike(String username) {
        return (root, query, cb) -> cb.like(root.get("username"), "%" + username + "%");
    }

    public static Specification<User> ageBetween(Integer min, Integer max) {
        return (root, query, cb) -> cb.between(root.get("age"), min, max);
    }
}

/**
 * 使用Specification查询
 */
@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public Page<User> search(UserSearchCriteria criteria, Pageable pageable) {
        Specification<User> spec = UserSpecifications.withCriteria(criteria);
        return userRepository.findAll(spec, pageable);
    }

    public List<User> findActiveAdults() {
        // 组合多个Specification
        Specification<User> spec = Specification
            .where(UserSpecifications.hasStatus(UserStatus.ACTIVE))
            .and(UserSpecifications.ageBetween(18, 60));

        return userRepository.findAll(spec);
    }
}

四、分页与排序

4.1 分页查询

java 复制代码
/**
 * 分页查询示例
 */
@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    /**
     * 基础分页查询
     */
    public Page<User> findAllWithPage(int page, int size) {
        Pageable pageable = PageRequest.of(page, size);
        return userRepository.findAll(pageable);
    }

    /**
     * 带排序的分页
     */
    public Page<User> findAllWithPageAndSort(int page, int size) {
        Sort sort = Sort.by(Sort.Direction.DESC, "createdAt");
        Pageable pageable = PageRequest.of(page, size, sort);
        return userRepository.findAll(pageable);
    }

    /**
     * 多字段排序
     */
    public Page<User> findWithMultiSort(int page, int size) {
        Sort sort = Sort.by(
            Sort.Order.desc("status"),
            Sort.Order.asc("username"),
            Sort.Order.desc("createdAt")
        );
        Pageable pageable = PageRequest.of(page, size, sort);
        return userRepository.findAll(pageable);
    }

    /**
     * 处理分页结果
     */
    public PageResult<UserDTO> findUsersPage(int page, int size) {
        Page<User> userPage = findAllWithPage(page, size);

        return PageResult.<UserDTO>builder()
            .content(userPage.getContent().stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList()))
            .totalElements(userPage.getTotalElements())
            .totalPages(userPage.getTotalPages())
            .currentPage(userPage.getNumber())
            .pageSize(userPage.getSize())
            .hasNext(userPage.hasNext())
            .hasPrevious(userPage.hasPrevious())
            .build();
    }

    private UserDTO convertToDTO(User user) {
        return UserDTO.builder()
            .id(user.getId())
            .username(user.getUsername())
            .email(user.getEmail())
            .build();
    }
}

/**
 * 分页结果封装
 */
@Data
@Builder
public class PageResult<T> {
    private List<T> content;
    private long totalElements;
    private int totalPages;
    private int currentPage;
    private int pageSize;
    private boolean hasNext;
    private boolean hasPrevious;
}

4.2 Slice分页

java 复制代码
/**
 * Slice分页(适用于无限滚动场景)
 */
public interface UserRepository extends JpaRepository<User, Long> {

    Slice<User> findByStatus(UserStatus status, Pageable pageable);
}

@Service
public class UserService {

    /**
     * Slice分页查询
     * 不查询总数,性能更好
     */
    public Slice<User> findByStatusSlice(UserStatus status, int page, int size) {
        Pageable pageable = PageRequest.of(page, size);
        return userRepository.findByStatus(status, pageable);
    }
}

五、实体关系映射

5.1 一对多/多对一

java 复制代码
/**
 * 部门实体(一方)
 */
@Entity
@Table(name = "departments")
@Data
public class Department {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    // 一对多:一个部门有多个员工
    @OneToMany(mappedBy = "department", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Employee> employees = new ArrayList<>();

    // 便捷方法
    public void addEmployee(Employee employee) {
        employees.add(employee);
        employee.setDepartment(this);
    }

    public void removeEmployee(Employee employee) {
        employees.remove(employee);
        employee.setDepartment(null);
    }
}

/**
 * 员工实体(多方)
 */
@Entity
@Table(name = "employees")
@Data
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    // 多对一:多个员工属于一个部门
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "department_id")
    private Department department;
}

5.2 多对多

java 复制代码
/**
 * 学生实体
 */
@Entity
@Table(name = "students")
@Data
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    // 多对多关系
    @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @JoinTable(
        name = "student_courses",
        joinColumns = @JoinColumn(name = "student_id"),
        inverseJoinColumns = @JoinColumn(name = "course_id")
    )
    private Set<Course> courses = new HashSet<>();

    public void addCourse(Course course) {
        courses.add(course);
        course.getStudents().add(this);
    }

    public void removeCourse(Course course) {
        courses.remove(course);
        course.getStudents().remove(this);
    }
}

/**
 * 课程实体
 */
@Entity
@Table(name = "courses")
@Data
public class Course {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToMany(mappedBy = "courses")
    private Set<Student> students = new HashSet<>();
}

5.3 一对一

java 复制代码
/**
 * 用户实体
 */
@Entity
@Table(name = "users")
@Data
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;

    // 一对一关系
    @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private UserProfile profile;

    public void setProfile(UserProfile profile) {
        this.profile = profile;
        profile.setUser(this);
    }
}

/**
 * 用户详情
 */
@Entity
@Table(name = "user_profiles")
@Data
public class UserProfile {

    @Id
    private Long id;

    private String avatar;
    private String bio;
    private String phone;

    @OneToOne(fetch = FetchType.LAZY)
    @MapsId
    @JoinColumn(name = "user_id")
    private User user;
}

六、审计功能

6.1 实体审计

java 复制代码
/**
 * 启用JPA审计
 */
@Configuration
@EnableJpaAuditing
public class JpaConfig {

    @Bean
    public AuditorAware<String> auditorProvider() {
        return () -> {
            // 从SecurityContext获取当前用户
            // return Optional.of(SecurityContextHolder.getContext()
            //     .getAuthentication().getName());
            return Optional.of("system");
        };
    }
}

/**
 * 审计基类
 */
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Data
public abstract class BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;

    @CreatedBy
    @Column(updatable = false)
    private String createdBy;

    @LastModifiedBy
    private String updatedBy;
}

/**
 * 继承审计基类
 */
@Entity
@Table(name = "articles")
@Data
@EqualsAndHashCode(callSuper = true)
public class Article extends BaseEntity {

    private String title;

    @Lob
    private String content;

    private Boolean published;
}

6.2 软删除

java 复制代码
/**
 * 支持软删除的基类
 */
@MappedSuperclass
@Data
public abstract class SoftDeleteEntity extends BaseEntity {

    @Column(name = "is_deleted")
    private Boolean deleted = false;

    private LocalDateTime deletedAt;

    private String deletedBy;
}

/**
 * 软删除Repository
 */
public interface SoftDeleteRepository<T extends SoftDeleteEntity, ID>
        extends JpaRepository<T, ID> {

    // 只查询未删除的数据
    @Query("SELECT e FROM #{#entityName} e WHERE e.deleted = false")
    List<T> findAllActive();

    @Query("SELECT e FROM #{#entityName} e WHERE e.id = :id AND e.deleted = false")
    Optional<T> findActiveById(@Param("id") ID id);

    // 软删除
    @Modifying
    @Query("UPDATE #{#entityName} e SET e.deleted = true, " +
           "e.deletedAt = CURRENT_TIMESTAMP WHERE e.id = :id")
    void softDelete(@Param("id") ID id);

    // 恢复
    @Modifying
    @Query("UPDATE #{#entityName} e SET e.deleted = false, " +
           "e.deletedAt = null WHERE e.id = :id")
    void restore(@Param("id") ID id);
}

七、实战案例

7.1 案例1:用户管理系统

java 复制代码
/**
 * 用户DTO
 */
@Data
@Builder
public class UserDTO {
    private Long id;
    private String username;
    private String email;
    private Integer age;
    private String status;
    private LocalDateTime createdAt;
}

/**
 * 创建用户请求
 */
@Data
public class CreateUserRequest {
    @NotBlank(message = "用户名不能为空")
    @Size(min = 3, max = 50)
    private String username;

    @NotBlank(message = "密码不能为空")
    @Size(min = 6, max = 100)
    private String password;

    @Email(message = "邮箱格式不正确")
    private String email;

    @Min(value = 1)
    @Max(value = 150)
    private Integer age;
}

/**
 * 用户服务
 */
@Service
@Transactional(readOnly = true)
@Slf4j
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
     * 创建用户
     */
    @Transactional
    public UserDTO createUser(CreateUserRequest request) {
        // 检查用户名是否存在
        if (userRepository.existsByUsername(request.getUsername())) {
            throw new BusinessException("用户名已存在");
        }

        User user = User.builder()
            .username(request.getUsername())
            .password(passwordEncoder.encode(request.getPassword()))
            .email(request.getEmail())
            .age(request.getAge())
            .status(UserStatus.ACTIVE)
            .build();

        User saved = userRepository.save(user);
        log.info("用户创建成功: {}", saved.getId());

        return convertToDTO(saved);
    }

    /**
     * 分页查询
     */
    public PageResult<UserDTO> findUsers(UserSearchCriteria criteria,
                                        int page, int size) {
        Pageable pageable = PageRequest.of(page, size,
            Sort.by(Sort.Direction.DESC, "createdAt"));

        Page<User> userPage = userRepository.findAll(
            UserSpecifications.withCriteria(criteria), pageable);

        return PageResult.<UserDTO>builder()
            .content(userPage.getContent().stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList()))
            .totalElements(userPage.getTotalElements())
            .totalPages(userPage.getTotalPages())
            .currentPage(page)
            .pageSize(size)
            .build();
    }

    /**
     * 批量更新状态
     */
    @Transactional
    public int batchUpdateStatus(List<Long> ids, UserStatus status) {
        return userRepository.batchUpdateStatus(ids, status);
    }

    private UserDTO convertToDTO(User user) {
        return UserDTO.builder()
            .id(user.getId())
            .username(user.getUsername())
            .email(user.getEmail())
            .age(user.getAge())
            .status(user.getStatus().name())
            .createdAt(user.getCreatedAt())
            .build();
    }
}

/**
 * 扩展Repository方法
 */
public interface UserRepository extends JpaRepository<User, Long>,
                                       JpaSpecificationExecutor<User> {

    boolean existsByUsername(String username);

    @Modifying
    @Query("UPDATE User u SET u.status = :status WHERE u.id IN :ids")
    int batchUpdateStatus(@Param("ids") List<Long> ids,
                         @Param("status") UserStatus status);
}

7.2 案例2:订单统计分析

java 复制代码
/**
 * 订单统计DTO
 */
@Data
@AllArgsConstructor
public class OrderStatistics {
    private String status;
    private Long count;
    private BigDecimal totalAmount;
}

/**
 * 日销售统计
 */
@Data
@AllArgsConstructor
public class DailySales {
    private LocalDate date;
    private Long orderCount;
    private BigDecimal totalAmount;
}

/**
 * 订单Repository
 */
public interface OrderRepository extends JpaRepository<Order, Long> {

    // 按状态统计
    @Query("SELECT new com.example.dto.OrderStatistics(o.status, COUNT(o), SUM(o.amount)) " +
           "FROM Order o GROUP BY o.status")
    List<OrderStatistics> getStatisticsByStatus();

    // 日销售统计
    @Query("SELECT new com.example.dto.DailySales(" +
           "CAST(o.createdAt AS LocalDate), COUNT(o), SUM(o.amount)) " +
           "FROM Order o " +
           "WHERE o.createdAt BETWEEN :start AND :end " +
           "GROUP BY CAST(o.createdAt AS LocalDate) " +
           "ORDER BY CAST(o.createdAt AS LocalDate)")
    List<DailySales> getDailySales(@Param("start") LocalDateTime start,
                                  @Param("end") LocalDateTime end);

    // 用户订单统计
    @Query("SELECT u.username, COUNT(o), SUM(o.amount) " +
           "FROM Order o JOIN o.user u " +
           "WHERE o.status = 'COMPLETED' " +
           "GROUP BY u.id, u.username " +
           "ORDER BY SUM(o.amount) DESC")
    List<Object[]> getTopCustomers(Pageable pageable);

    // 原生SQL复杂查询
    @Query(value = "SELECT DATE(created_at) as date, " +
           "COUNT(*) as order_count, " +
           "SUM(amount) as total_amount " +
           "FROM orders " +
           "WHERE created_at >= :start " +
           "GROUP BY DATE(created_at) " +
           "ORDER BY date DESC " +
           "LIMIT :limit", nativeQuery = true)
    List<Object[]> getRecentDailySales(@Param("start") LocalDateTime start,
                                       @Param("limit") int limit);
}

/**
 * 订单统计服务
 */
@Service
@Transactional(readOnly = true)
public class OrderStatisticsService {

    @Autowired
    private OrderRepository orderRepository;

    /**
     * 获取订单状态分布
     */
    public Map<String, OrderStatistics> getStatusDistribution() {
        return orderRepository.getStatisticsByStatus().stream()
            .collect(Collectors.toMap(OrderStatistics::getStatus, Function.identity()));
    }

    /**
     * 获取日销售报表
     */
    public List<DailySales> getDailySalesReport(int days) {
        LocalDateTime end = LocalDateTime.now();
        LocalDateTime start = end.minusDays(days);
        return orderRepository.getDailySales(start, end);
    }

    /**
     * 获取Top客户
     */
    public List<Map<String, Object>> getTopCustomers(int limit) {
        Pageable pageable = PageRequest.of(0, limit);
        List<Object[]> results = orderRepository.getTopCustomers(pageable);

        return results.stream().map(row -> {
            Map<String, Object> map = new HashMap<>();
            map.put("username", row[0]);
            map.put("orderCount", row[1]);
            map.put("totalAmount", row[2]);
            return map;
        }).collect(Collectors.toList());
    }
}

7.3 案例3:自定义Repository实现

java 复制代码
/**
 * 自定义Repository接口
 */
public interface CustomUserRepository {
    List<User> findByComplexCriteria(UserSearchCriteria criteria);
    void batchInsert(List<User> users);
}

/**
 * 自定义Repository实现
 */
@Repository
public class CustomUserRepositoryImpl implements CustomUserRepository {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public List<User> findByComplexCriteria(UserSearchCriteria criteria) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<User> query = cb.createQuery(User.class);
        Root<User> root = query.from(User.class);

        List<Predicate> predicates = new ArrayList<>();

        if (StringUtils.hasText(criteria.getUsername())) {
            predicates.add(cb.like(root.get("username"),
                "%" + criteria.getUsername() + "%"));
        }

        if (criteria.getStatus() != null) {
            predicates.add(cb.equal(root.get("status"), criteria.getStatus()));
        }

        query.where(predicates.toArray(new Predicate[0]));
        query.orderBy(cb.desc(root.get("createdAt")));

        return entityManager.createQuery(query).getResultList();
    }

    @Override
    @Transactional
    public void batchInsert(List<User> users) {
        int batchSize = 50;
        for (int i = 0; i < users.size(); i++) {
            entityManager.persist(users.get(i));

            if (i > 0 && i % batchSize == 0) {
                entityManager.flush();
                entityManager.clear();
            }
        }
        entityManager.flush();
        entityManager.clear();
    }
}

/**
 * 组合自定义Repository
 */
public interface UserRepository extends JpaRepository<User, Long>,
                                       JpaSpecificationExecutor<User>,
                                       CustomUserRepository {
}

八、性能优化

8.1 N+1问题解决

java 复制代码
/**
 * N+1问题示例与解决
 */
public interface OrderRepository extends JpaRepository<Order, Long> {

    // ❌ 会产生N+1问题
    List<Order> findByStatus(OrderStatus status);

    // ✓ 使用JOIN FETCH解决
    @Query("SELECT o FROM Order o JOIN FETCH o.user WHERE o.status = :status")
    List<Order> findByStatusWithUser(@Param("status") OrderStatus status);

    // ✓ 使用EntityGraph
    @EntityGraph(attributePaths = {"user"})
    List<Order> findWithUserByStatus(OrderStatus status);
}

/**
 * EntityGraph定义
 */
@Entity
@NamedEntityGraph(
    name = "Order.withUser",
    attributeNodes = @NamedAttributeNode("user")
)
public class Order {
    // ...
}

// 使用命名EntityGraph
@EntityGraph(value = "Order.withUser")
List<Order> findByStatus(OrderStatus status);

8.2 批量操作优化

java 复制代码
/**
 * 批量操作配置
 */
// application.yml
/*
spring:
  jpa:
    properties:
      hibernate:
        jdbc:
          batch_size: 50
        order_inserts: true
        order_updates: true
*/

/**
 * 批量操作服务
 */
@Service
public class BatchService {

    @PersistenceContext
    private EntityManager entityManager;

    /**
     * 批量插入
     */
    @Transactional
    public void batchInsert(List<User> users) {
        int batchSize = 50;

        for (int i = 0; i < users.size(); i++) {
            entityManager.persist(users.get(i));

            if ((i + 1) % batchSize == 0) {
                entityManager.flush();
                entityManager.clear();
            }
        }

        entityManager.flush();
        entityManager.clear();
    }

    /**
     * 批量更新(使用JPQL)
     */
    @Transactional
    public int batchUpdateStatus(List<Long> ids, UserStatus status) {
        return entityManager.createQuery(
            "UPDATE User u SET u.status = :status WHERE u.id IN :ids")
            .setParameter("status", status)
            .setParameter("ids", ids)
            .executeUpdate();
    }

    /**
     * 批量删除
     */
    @Transactional
    public int batchDelete(List<Long> ids) {
        return entityManager.createQuery(
            "DELETE FROM User u WHERE u.id IN :ids")
            .setParameter("ids", ids)
            .executeUpdate();
    }
}

8.3 只读优化

java 复制代码
/**
 * 只读事务优化
 */
@Service
@Transactional(readOnly = true)  // 类级别只读
public class ReportService {

    @Autowired
    private OrderRepository orderRepository;

    /**
     * 只读查询(不需要脏检查)
     */
    public List<OrderStatistics> getReport() {
        return orderRepository.getStatisticsByStatus();
    }

    /**
     * 写操作需要覆盖
     */
    @Transactional  // 覆盖为读写事务
    public void updateOrder(Order order) {
        orderRepository.save(order);
    }
}

/**
 * 使用投影减少数据传输
 */
public interface UserProjection {
    Long getId();
    String getUsername();
    String getEmail();
}

public interface UserRepository extends JpaRepository<User, Long> {

    // 只查询需要的字段
    List<UserProjection> findProjectedByStatus(UserStatus status);

    // DTO投影
    @Query("SELECT new com.example.dto.UserDTO(u.id, u.username, u.email) " +
           "FROM User u WHERE u.status = :status")
    List<UserDTO> findDTOByStatus(@Param("status") UserStatus status);
}

九、最佳实践

9.1 Repository设计原则

java 复制代码
/**
 * Repository最佳实践
 */

// 1. 使用合适的返回类型
public interface UserRepository extends JpaRepository<User, Long> {

    // 单个结果用Optional
    Optional<User> findByUsername(String username);

    // 集合结果用List
    List<User> findByStatus(UserStatus status);

    // 存在性检查用boolean
    boolean existsByEmail(String email);

    // 计数用long
    long countByStatus(UserStatus status);
}

// 2. 避免暴露实体,使用DTO
public interface UserRepository extends JpaRepository<User, Long> {

    @Query("SELECT new com.example.dto.UserDTO(u.id, u.username, u.email) " +
           "FROM User u WHERE u.id = :id")
    Optional<UserDTO> findDTOById(@Param("id") Long id);
}

// 3. 合理使用懒加载
@Entity
public class Order {

    @ManyToOne(fetch = FetchType.LAZY)  // 默认EAGER,改为LAZY
    @JoinColumn(name = "user_id")
    private User user;
}

9.2 事务管理

java 复制代码
/**
 * 事务最佳实践
 */
@Service
public class OrderService {

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private UserRepository userRepository;

    /**
     * 正确的事务边界
     */
    @Transactional
    public Order createOrder(Long userId, CreateOrderRequest request) {
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new EntityNotFoundException("用户不存在"));

        Order order = Order.builder()
            .orderNo(generateOrderNo())
            .user(user)
            .amount(request.getAmount())
            .status(OrderStatus.PENDING)
            .build();

        return orderRepository.save(order);
    }

    /**
     * 事务传播
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void processOrder(Long orderId) {
        // 在同一事务中执行
        updateOrderStatus(orderId, OrderStatus.PROCESSING);
        sendNotification(orderId);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logOperation(String message) {
        // 独立事务,不受外部事务影响
    }
}

十、总结

核心知识点回顾

css 复制代码
Spring Data JPA核心要点
│
├── 基础概念
│   ├── Repository接口体系
│   ├── 实体映射
│   └── 配置方式
│
├── 查询方法
│   ├── 方法名派生查询
│   ├── @Query注解
│   └── Specification动态查询
│
├── 分页排序
│   ├── Pageable/Page
│   ├── Sort
│   └── Slice
│
├── 实体关系
│   ├── 一对多/多对一
│   ├── 多对多
│   └── 一对一
│
├── 审计功能
│   ├── 创建/修改时间
│   ├── 创建/修改人
│   └── 软删除
│
├── 性能优化
│   ├── N+1问题解决
│   ├── 批量操作
│   └── 只读优化
│
└── 最佳实践
    ├── Repository设计
    ├── 事务管理
    └── 投影查询

Spring Data JPA极大简化了数据访问层的开发,掌握其核心概念和使用技巧,能够帮助我们高效地开发数据库应用。在实际项目中,要注意性能优化和最佳实践的应用。


相关推荐
一嘴一个橘子8 小时前
mybatis - 动态语句、批量注册mapper、分页插件
java
组合缺一8 小时前
Json Dom 怎么玩转?
java·json·dom·snack4
危险、8 小时前
一套提升 Spring Boot 项目的高并发、高可用能力的 Cursor 专用提示词
java·spring boot·提示词
kaico20188 小时前
JDK11新特性
java
钊兵8 小时前
java实现GeoJSON地理信息对经纬度点的匹配
java·开发语言
jiayong239 小时前
Tomcat性能优化面试题
java·性能优化·tomcat
爬山算法9 小时前
Hibernate(51)Hibernate的查询缓存如何使用?
spring·缓存·hibernate
秋刀鱼程序编程9 小时前
Java基础入门(五)----面向对象(上)
java·开发语言
纪莫9 小时前
技术面:MySQL篇(InnoDB的锁机制)
java·数据库·java面试⑧股
Remember_9939 小时前
【LeetCode精选算法】滑动窗口专题二
java·开发语言·数据结构·算法·leetcode