首页 / Spring Boot 入门教程 / Spring Data JPA 入门

Spring Boot 入门教程

Spring Data JPA 入门

本教程共 48 篇 · 第 27 篇 · 更新于 2026-08-13 · 约 6 分钟阅读

Spring BootSpring Data JPA实体映射RepositoryCRUDHibernate派生查询

本节目标:理解 ORM 和 JPA 的关系,会写实体类和 Repository 接口,用方法名自动生成查询,完成基本 CRUD。

从对象到表:ORM 在做什么

上一节用 JdbcTemplate 写 SQL,字段一多就很啰嗦。每次都要写 insert into product (name, price) values (?, ?),查出来还要手动 rs.getString 赋值。

ORM(对象关系映射)换个思路:让 Java 对象直接对应数据库表,字段对应列。你操作对象,框架翻译成 SQL。

JPA 是 Java 官方的 ORM 规范,只定义接口和注解,不提供实现。Hibernate 是最流行的 JPA 实现。Spring Data JPA 再进一步:连实现类都不用写,你定义接口,它运行时帮你生成。

三层关系捋一遍:JPA 是标准(规矩),Hibernate 是标准的一个实现(干活的人),Spring Data JPA 是 Spring 家族的便捷封装(发工资的管家)。

引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

一个 starter 带齐了三样东西:Hibernate(JPA 实现)、Spring Data JPA(仓库支持)、Spring ORM(事务集成)。

实体类:一张表的 Java 画像

实体类就是「表」的 Java 版本。每个实例对应一行数据:

package com.example.demo.book;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

import java.math.BigDecimal;
import java.time.LocalDate;

@Entity
@Table(name = "books")
public class Book {

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

    @Column(nullable = false, length = 100)
    private String title;

    private BigDecimal price;

    private LocalDate publishDate;

    protected Book() {
        // JPA 需要无参构造,protected 即可
    }

    public Book(String title, BigDecimal price, LocalDate publishDate) {
        this.title = title;
        this.price = price;
        this.publishDate = publishDate;
    }

    // getter / setter
    public Long getId() { return id; }
    public String getTitle() { return title; }
    public BigDecimal getPrice() { return price; }
    public LocalDate getPublishDate() { return publishDate; }
}

几个注解各司其职:

  • @Entity:声明这是 JPA 实体,要建表。
  • @Table(name = "books"):指定表名。不写就用类名。
  • @Id:主键,必须有。
  • @GeneratedValue:主键生成策略,IDENTITY 表示数据库自增。
  • @Column:列细节,比如非空、长度。不写注解的字段也会映射,列名默认是属性名。

表名和列名的默认规则是「类名/属性名原样用」。实体 Book 对应表 book,属性 publishDate 对应列 publishDate。想让列名变成下划线风格,可以显式写:

@Column(name = "publish_date")
private LocalDate publishDate;
Note

JPA 规范要求实体有无参构造。你写了带参构造后,无参构造会被覆盖,必须手动补一个。这是初学者最常见的报错来源。

命名空间统一用 jakarta.persistence.*。Spring Boot 3 之前是 javax.persistence.*,4.x 全线 jakarta,看到旧教程别照抄。

字段类型怎么映射

Java 类型和数据库类型的对应,JPA 基本自动处理:

Java 类型数据库类型说明
StringVARCHAR可加 length 限制
int/IntegerINT
long/LongBIGINT主键常用
BigDecimalDECIMAL金额必用,别用 double
LocalDateDATE日期
LocalDateTimeTIMESTAMP日期时间
booleanBOOLEAN

枚举要单独处理。默认存的是枚举的 name()(字符串),想要更稳定的存储,用 @Enumerated:

public enum BookStatus {
    DRAFT, PUBLISHED, ARCHIVED
}

@Enumerated(EnumType.STRING)  // 存字符串 DRAFT/PUBLISHED
private BookStatus status;

EnumType.ORDINAL 存数字下标,字段顺序一变数据就错位,生产环境别用。

主键生成策略 @GeneratedValue 的取值也值得花两句:IDENTITY 依赖数据库自增,MySQL 常用;AUTO 让 Hibernate 按数据库选;UUID 生成字符串主键,适合分布式分库分表场景。单表应用 IDENTITY 最省心。

Repository:只写接口,不写实现

Spring Data JPA 的核心魔法在这里。定义一个接口,继承 JpaRepository:

package com.example.demo.book;

import org.springframework.data.jpa.repository.JpaRepository;

public interface BookRepository extends JpaRepository<Book, Long> {
}

两个泛型参数:实体类型 Book,主键类型 Long。什么都不用写,就能用了。

@Service
public class BookService {

    private final BookRepository bookRepository;

    public BookService(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    public Book create(Book book) {
        return bookRepository.save(book);
    }

    public Optional<Book> findById(Long id) {
        return bookRepository.findById(id);
    }

    public List<Book> findAll() {
        return bookRepository.findAll();
    }

    public void deleteById(Long id) {
        bookRepository.deleteById(id);
    }
}

JpaRepository 自带的 CRUD 方法够日常用:savefindByIdfindAlldeleteByIdcountexistsByIdfindById 返回 Optional,提醒你处理「查不到」的情况,别直接 get()

save 的语义值得说清楚:它兼做新增和更新。判断依据是主键——主键为 null 就执行 INSERT,主键有值就执行 UPDATE。所以更新前要把 id 塞进实体里。

每次 Repository 调用都在一个隐式事务里。单条操作没问题;多条操作要保证原子性,就在 Service 方法上加 @Transactional(第 26 章讲过)。

save 还有个小坑:传进来的实体和返回的实体不一定是同一个。JPA 可能返回托管后的新实例,所以要用返回值接住:book = bookRepository.save(book);

派生查询:方法名就是 SQL

想按条件查?给接口加个方法,方法名就是查询规则:

public interface BookRepository extends JpaRepository<Book, Long> {

    // 按书名精确查询
    List<Book> findByTitle(String title);

    // 按价格区间查询:小于等于
    List<Book> findByPriceLessThanEqual(BigDecimal price);

    // 组合条件:书名 + 出版日期之后
    List<Book> findByTitleAndPublishDateAfter(String title, LocalDate date);

    // 忽略大小写
    List<Book> findByTitleIgnoreCase(String title);

    // 排序:价格降序
    List<Book> findByPriceGreaterThanOrderByPriceDesc(BigDecimal price);

    // 统计
    long countByTitle(String title);
}

Spring Data 解析方法名,自动生成实现。规则拆开看很简单:findBy 开头,后面跟属性名,And/Or 连接多个条件,LessThanAfterGreaterThan 是比较操作符。

常用操作符再列一张表:

关键字示例生成的 SQL 语义
ContainingfindByTitleContainingLIKE %值%
StartingWithfindByTitleStartingWithLIKE 值%
BetweenfindByPriceBetweenBETWEEN 两个值
InfindByIdInIN 列表
IsNull/IsNotNullfindByAuthorIsNullIS NULL / IS NOT NULL
OrderBy...Asc/DescfindByPriceOrderByPriceDesc排序
Top/FirstfindFirst3ByOrderByPriceDesc限制条数

findFirst3ByOrderByPriceDesc 这种「取最贵的前三本」的需求,一行方法名就表达了。

Warning

属性名必须和实体字段完全一致(驼峰命名)。写错启动不报错,运行时才报「无法解析属性」,排查时先对字段名。

跑通第一个完整示例

学了就敲,敲了就跑。加一个启动即执行的 Runner,验证整条链路:

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class BookDataRunner implements CommandLineRunner {

    private final BookRepository bookRepository;

    public BookDataRunner(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    @Override
    public void run(String... args) {
        // 新增
        bookRepository.save(new Book("Spring Boot 入门", new BigDecimal("59.00"), LocalDate.of(2026, 6, 1)));
        bookRepository.save(new Book("Java 核心技术", new BigDecimal("99.00"), LocalDate.of(2025, 3, 15)));

        // 查询
        System.out.println(bookRepository.findByTitle("Spring Boot 入门"));
        System.out.println(bookRepository.findByPriceLessThanEqual(new BigDecimal("80")));
        System.out.println("总数:" + bookRepository.count());
    }
}

CommandLineRunner 在应用启动完成后执行,是演示数据访问的最快方式。跑 mvn spring-boot:run,控制台就能看到查询结果,配合 show-sql: true 还能观察 Hibernate 生成的 SQL。

配置:开发环境最常用设置

application.yml 里两行配置,开发调试必备:

spring:
  jpa:
    hibernate:
      ddl-auto: update   # 启动时按实体自动建表/更新表
    show-sql: true       # 控制台打印生成的 SQL

ddl-auto 有五个取值,开发用 updatecreate-drop,生产要慎重:

取值行为建议
none不生成表生产环境
update有差异就更新表结构开发方便
create每次启动先删后建会丢数据
create-drop启动建表,关闭删表测试
validate只校验表结构是否匹配生产可选
Tip

启动日志里出现 spring.jpa.open-in-view is enabled by default 的警告时,在配置里加 spring.jpa.open-in-view: false。视图层懒加载是历史包袱,REST 项目基本用不上,关掉更省心。

实体扫描:约定优于配置

Spring Boot 自动扫描主类所在包及其子包下的 @Entity。所以实体类别乱放,放在主类的子包里就对了。

想自定义扫描范围,用 @EntityScan 指定包。Repository 接口同理,Spring Boot 自动扫描,需要手动指定时用 @EnableJpaRepositories

小结

JPA 把表和对象对应起来,Spring Data JPA 把实现类也省了。三步走:写实体类、写 Repository 接口、注入使用。派生查询靠方法名生成 SQL,简单条件不用写一句 SQL。

本章示例都基于 H2 内存库。换成 MySQL 只需改数据源配置,实体和 Repository 一行不动——这就是 JPA 屏蔽数据库差异的价值。

下一节进阶:分页、排序,以及用 @Query 写自定义查询。