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


相关推荐
虚伪的空想家1 小时前
首发:TDengine3.3.6版本使用K8S部署
java·docker·容器·kubernetes·k8s·时序数据库·tdengine
悟空码字1 小时前
SpringBoot实现系统监控:给应用装上“健康手环”
java·后端·监控
葡萄成熟时 !1 小时前
快捷键idea
java
吃喝不愁霸王餐APP开发者1 小时前
外卖霸王餐灰度开关:基于Spring Cloud Config+Bus动态刷新踩坑
java
雨中飘荡的记忆1 小时前
Spring Security详解
java·spring
小许学java1 小时前
网络编程套接字
java·网络·udp·socket·tcp·套接字
向葭奔赴♡1 小时前
Android AlertDialog实战:5种常用对话框实现
android·java·开发语言·贪心算法·gitee
坐不住的爱码1 小时前
静态资源映射-spring整合
java·spring·状态模式
大佐不会说日语~1 小时前
基于Spring AI Alibaba的AI聊天系统中,流式输出暂停时出现重复插入问题的分析与解决
java·人工智能·spring