Spring Data JPA 实战指南

 更新时间:2026年04月30日 08:12:50   作者:014-code  
本文主要介绍了Spring Data JPA 实战指南,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

用过 MyBatis 再用 JPA,可能会觉得 JPA 很别扭——SQL 都不用写了,框架自动搞定。

但用久了会发现,JPA 写起来其实很爽,尤其单表操作,几乎不需要写 SQL。

基础配置

依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>

配置

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test?useSSL=false
    username: root
    password: 123456
  jpa:
    hibernate:
      ddl-auto: update  # 开发用 update,生产用 validate
    show-sql: true      # 打印 SQL
    properties:
      hibernate:
        format_sql: true

ddl-auto 几个选项:

  • update:自动更新表结构(不会删数据)
  • create:每次启动删表重建
  • validate:只验证,不改表
  • none:什么都不做

实体映射

基本映射

@Entity
@Table(name = "sys_user")
@Data
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name = "user_name", length = 50, nullable = false)
    private String userName;
    private Integer age;
    @Column(columnDefinition = "varchar(20) default 'ACTIVE'")
    private String status;
    @Column(name = "create_time")
    private LocalDateTime createTime;
    @Transient  // 不映射到数据库
    private String tempField;
}

主键生成策略

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)  // MySQL 自增
// 或
@GeneratedValue(strategy = GenerationType.SEQUENCE)  // Oracle、序列
// 或
@GeneratedValue(strategy = GenerationType.UUID)      // UUID
private String id;

Repository 基础 CRUD

继承 JpaRepository 就有基本的增删改查:

public interface UserRepository extends JpaRepository<User, Long> {
    // 继承来的方法:
    // save(entity)       - 保存或更新
    // findById(id)      - 查询
    // deleteById(id)    - 删除
    // count()           - 计数
    // existsById(id)    - 是否存在
}
@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;
    public User createUser(String name, Integer age) {
        User user = new User();
        user.setUserName(name);
        user.setAge(age);
        return userRepository.save(user);  // 自动 insert
    }
    public Optional<User> getUser(Long id) {
        return userRepository.findById(id);  // 自动 select
    }
    public void deleteUser(Long id) {
        userRepository.deleteById(id);  // 自动 delete
    }
    public User updateUser(Long id, String newName) {
        User user = userRepository.findById(id)
            .orElseThrow(() -> new RuntimeException("用户不存在"));
        user.setUserName(newName);
        return userRepository.save(user);  // 自动 update
    }
}

方法名查询(单表神器)

这是 JPA 最爽的地方——不用写 SQL,方法名就是查询

基础规则

public interface UserRepository extends JpaRepository<User, Long> {
    // 等值查询:findBy + 字段名
    List<User> findByUserName(String userName);
    // → SELECT * FROM sys_user WHERE user_name = ?
    // 模糊查询:findBy + 字段名 + Like
    List<User> findByUserNameLike(String userName);
    // → SELECT * FROM sys_user WHERE user_name LIKE ?
    // 多条件:findBy + 字段1 + And + 字段2
    List<User> findByUserNameAndAge(String userName, Integer age);
    // → SELECT * FROM sys_user WHERE user_name = ? AND age = ?
    // Or 条件
    List<User> findByUserNameOrAge(String userName, Integer age);
    // → SELECT * FROM sys_user WHERE user_name = ? OR age = ?
}

常用关键词

// 大于/小于/等于
List<User> findByAgeGreaterThan(Integer age);      // age > ?
List<User> findByAgeLessThan(Integer age);        // age < ?
List<User> findByAgeGreaterThanEqual(Integer age); // age >= ?
List<User> findByStatusEquals(String status);      // status = ?
// BETWEEN 范围
List<User> findByAgeBetween(Integer min, Integer max);
// → WHERE age BETWEEN ? AND ?
// IN 查询
List<User> findByUserNameIn(List<String> names);
// → WHERE user_name IN (?, ?, ?)
// NULL 判断
List<User> findByEmailIsNull();
List<User> findByEmailIsNotNull();
// True/False
List<User> findByActiveTrue();   // WHERE active = true
List<User> findByActiveFalse();  // WHERE active = false
// 排序
List<User> findByStatusOrderByAgeDesc(String status);
// → WHERE status = ? ORDER BY age DESC

分页和排序

// 分页:参数加 Pageable
Page<User> findByStatus(String status, Pageable pageable);
// 排序:Sort
List<User> findByStatus(String status, Sort sort);
// 使用
@Service
public class UserService {
    public Page<User> getUserPage(int page, int size) {
        Pageable pageable = PageRequest.of(page, size, Sort.by("age").descending());
        return userRepository.findByStatus("ACTIVE", pageable);
    }
}

@Query 自定义查询

方法名解决不了的,用 @Query

JPQL 查询

@Query("SELECT u FROM User u WHERE u.userName = ?1")
User findByName(String userName);
// 占位符 ?1、?2 按参数顺序
@Query("SELECT u FROM User u WHERE u.userName = ?1 AND u.age > ?2")
List<User> findByNameAndAge(String name, Integer age);
// 命名参数(更清晰)
@Query("SELECT u FROM User u WHERE u.userName = :name AND u.age > :age")
List<User> findByNameAndAgeV2(@Param("name") String name, @Param("age") Integer age);

多表联查

假设 User 关联 Department:

@Entity
public class User {
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "dept_id")
    private Department department;
}
// 查询用户并带出部门名称
@Query("SELECT u FROM User u JOIN FETCH u.department WHERE u.id = :id")
User findByIdWithDepartment(@Param("id") Long id);

原生 SQL

@Query(value = "SELECT * FROM sys_user WHERE user_name = :name", nativeQuery = true)
User findByNameNative(@Param("name") String name);
// 原生 SQL 分页(注意 countQuery)
@Query(
    value = "SELECT * FROM sys_user WHERE status = :status ORDER BY age DESC",
    countQuery = "SELECT count(*) FROM sys_user WHERE status = :status",
    nativeQuery = true
)
Page<User> findByStatusPage(@Param("status") String status, Pageable pageable);

@Modifying 修改操作

@Modifying
@Query("UPDATE User u SET u.status = :status WHERE u.id = :id")
int updateStatus(@Param("id") Long id, @Param("status") String status);
@Modifying
@Query("DELETE FROM User u WHERE u.status = 'INACTIVE'")
void deleteInactiveUsers();

记得在 Service 层加事务:

@Transactional
public void updateStatus(Long id, String status) {
    userRepository.updateStatus(id, status);
}

一对多 / 多对一

实体定义

@Entity
@Table(name = "department")
@Data
public class Department {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @OneToMany(mappedBy = "department", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<User> users;
}
@Entity
@Table(name = "sys_user")
@Data
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String userName;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "dept_id")
    private Department department;
}

查询

// 根据部门查用户
List<User> findByDepartmentId(Long deptId);

// 根据用户查部门(直接访问属性就行)
User user = userRepository.findById(id).orElseThrow(...);
String deptName = user.getDepartment().getName();  // 懒加载,会再查一次

// 一次性查出来(解决 N+1)
@Query("SELECT u FROM User u JOIN FETCH u.department WHERE u.id = :id")
User findByIdWithDept(@Param("id") Long id);

常见问题

1. 懒加载异常

// 报错:LazyInitializationException
User user = userRepository.findById(1L).orElseThrow(...);
String deptName = user.getDepartment().getName();  // session 已关闭

解决:在 Service 层加 @Transactional,或者用 JOIN FETCH 一次性加载。

2. N+1 问题

// N+1:查 1 个用户,再查 N 次部门
List<User> users = userRepository.findAll();
// SELECT * FROM sys_user
// SELECT * FROM department WHERE id = ?
// SELECT * FROM department WHERE id = ?
// ...

解决:用 @EntityGraphJOIN FETCH

@EntityGraph(attributePaths = {"department"})
List<User> findAllWithDept();

// 或
@Query("SELECT u FROM User u JOIN FETCH u.department")
List<User> findAllWithDept();

3. save 和 update 的区别

// save() - 如果 id 已存在就是 update,不存在就是 insert
User user = new User();
user.setId(1L);  // id 存在,变成 update
user.setUserName("newName");
userRepository.save(user);  // UPDATE

总结

方式适用场景
继承 JpaRepository基本 CRUD
方法名查询单表简单查询
@Query + JPQL多表关联、复杂查询
@Query + 原生 SQL特定数据库语法
JOIN FETCH解决懒加载和 N+1

JPA 最大的好处是不用写 SQL,单表操作特别爽。但多表关联和复杂查询,还是得用 @Query。用久了会发现,JPA + @Query 配合起来,效率比 MyBatis 高多了。

到此这篇关于Spring Data JPA 实战指南的文章就介绍到这了,更多相关Spring Data JPA 实战内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java面向国际化项目开发需遵循的命名规范

    java面向国际化项目开发需遵循的命名规范

    这篇文章主要为大家介绍了在参与开发国际化项目时需遵循的java命名规范,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2022-03-03
  • SpringBoot静态方法调用Spring容器bean的三种解决方案

    SpringBoot静态方法调用Spring容器bean的三种解决方案

    在SpringBoot中静态方法调用Spring容器bean时出现的null值问题,本文就来介绍一下SpringBoot静态方法调用Spring容器bean的三种解决方案,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2025-01-01
  • Java中BigDecimal与0比较的一个坑实战记录

    Java中BigDecimal与0比较的一个坑实战记录

    BigDecimal属于大数据,精度极高,不属于基本数据类型,属于java对象,下面这篇文章主要给大家介绍了关于Java中BigDecimal与0比较的一个坑的相关资料,需要的朋友可以参考下
    2022-12-12
  • Java如何基于okhttp请求SSE接口流式返回详解

    Java如何基于okhttp请求SSE接口流式返回详解

    对于流式返回,Spring Boot提供了两种不同的方式,下面这篇文章主要给大家介绍了关于Java如何基于okhttp请求SSE接口流式返回的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-03-03
  • SpringBoot中WebClient的实践过程

    SpringBoot中WebClient的实践过程

    本文介绍了SpringBoot中WebClient的使用,包括配置、使用场景以及优化策略,帮助开发者更高效地进行服务间通信,WebClient具有非阻塞式I/O、强大的功能、灵活性等优点,适用于高并发场景,通过合理配置和优化,可以显著提升服务间通信的效率和可靠性
    2025-12-12
  • Java 栈和队列的相互转换详解

    Java 栈和队列的相互转换详解

    栈和队列,严格意义上来说,也属于线性表,因为它们也都用于存储逻辑关系为 "一对一" 的数据,但由于它们比较特殊,因此将其单独作为一章,做重点讲解
    2022-02-02
  • java实现大文件分割与合并的实例代码

    java实现大文件分割与合并的实例代码

    java实现大文件分割与合并的实例代码,需要的朋友可以参考一下
    2013-03-03
  • SpringBoot整合MQTT小结汇总

    SpringBoot整合MQTT小结汇总

    MQTT 客户端是运行 MQTT 库并通过网络连接到 MQTT 代理的任何设备,是一种基于发布/订阅(publish/subscribe)模式的“轻量级”通讯协议,该协议构建于 TCP/IP 协议上,由 IBM 于 1999 年发明,对SpringBoot整合MQTT相关知识感兴趣的朋友一起看看吧
    2022-01-01
  • 四个Java常见分布式锁的选型和性能对比

    四个Java常见分布式锁的选型和性能对比

    当涉及到分布式系统中的并发控制和数据一致性时,分布式锁是一种常见的解决方案,本文将对几种常见的分布式锁实现原理、实现示例、应用场景以及优缺点进行详细分析,需要的可以参考一下
    2023-05-05
  • Spring Cloud Config 使用本地配置文件方式

    Spring Cloud Config 使用本地配置文件方式

    这篇文章主要介绍了Spring Cloud Config 使用本地配置文件方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07

最新评论