Spring Data JPA 实战指南

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

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

基础配置

依赖

xml 复制代码
<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>

配置

yaml 复制代码
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:什么都不做

实体映射

基本映射

java 复制代码
@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;
}

主键生成策略

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

Repository 基础 CRUD

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

java 复制代码
public interface UserRepository extends JpaRepository<User, Long> {
    // 继承来的方法:
    // save(entity)       - 保存或更新
    // findById(id)      - 查询
    // deleteById(id)    - 删除
    // count()           - 计数
    // existsById(id)    - 是否存在
}
java 复制代码
@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,方法名就是查询

基础规则

java 复制代码
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 = ?
}

常用关键词

java 复制代码
// 大于/小于/等于
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

分页和排序

java 复制代码
// 分页:参数加 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 查询

java 复制代码
@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:

java 复制代码
@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

java 复制代码
@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 修改操作

java 复制代码
@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 层加事务:

java 复制代码
@Transactional
public void updateStatus(Long id, String status) {
    userRepository.updateStatus(id, status);
}

一对多 / 多对一

实体定义

java 复制代码
@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;
}

查询

java 复制代码
// 根据部门查用户
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. 懒加载异常

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

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

2. N+1 问题

java 复制代码
// 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

java 复制代码
@EntityGraph(attributePaths = {"department"})
List<User> findAllWithDept();

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

3. save 和 update 的区别

java 复制代码
// 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 高多了。

相关推荐
安小牛2 小时前
Android 开发汉字转带声调的拼音
android·java·学习·android studio
聚美智数2 小时前
企业实际控制人查询-公司实控人查询
android·java·javascript
zb200641202 小时前
SpringBoot详解
java·spring boot·后端
AI人工智能+电脑小能手2 小时前
【大白话说Java面试题】【Java基础篇】第7题:HashMap的get流程是什么
java·后端·面试·哈希算法·散列表·hash-index·hash
我头发多我先学3 小时前
C++ 模板全解:从泛型编程初阶到特化、分离编译进阶
java·开发语言·c++
mfxcyh3 小时前
使用MobaXterm配置nginx
java·服务器·nginx
木叶子---3 小时前
Spring 枚举转换器冲突问题分析与解决
java·python·spring
standovon3 小时前
SpringSecurity的配置
java
霸道流氓气质3 小时前
SpringBoot+LangChain4j+Ollama+RAG(检索增强生成)实现私有文档向量化检索回答
java·spring boot·后端