Java Spring Data JPA 实战指南:Repository 查询、分页与实体映射

作者:唐青枫日期:2026/6/10

简介

Spring Data JPA 是 Spring Data 家族里专门用来简化 JPA 开发的模块。

它不是一个新的 ORM 规范。

更准确地说:

1JPA 是规范
2Hibernate 是常见实现
3Spring Data JPA  Spring  JPA Repository 的封装
4

在 Spring Boot 项目里,常见调用链大致是:

1Controller
2  |
3  v
4Service
5  |
6  v
7Repository
8  |
9  v
10Spring Data JPA
11  |
12  v
13Hibernate
14  |
15  v
16JDBC
17  |
18  v
19数据库
20

它的核心目标是:

1 Repository 接口表达数据访问,让常见 CRUD、分页、排序、简单查询少写很多样板代码。
2

一句话概括:

1Spring Data JPA 适合用实体对象驱动数据库操作,尤其适合标准 CRUD、分页列表、简单条件查询和领域模型比较清楚的项目。
2

JPA、Hibernate、Spring Data JPA 的关系

这几个概念经常一起出现。

JPA

JPA 全称是 Jakarta Persistence API

它是一套持久化规范。

常见注解有:

  • @Entity
  • @Table
  • @Id
  • @GeneratedValue
  • @Column
  • @OneToMany
  • @ManyToOne

常见接口有:

  • EntityManager
  • Query
  • TypedQuery

Hibernate

HibernateJPA 的常见实现。

它负责真正执行对象映射、SQL 生成、脏检查、缓存、关联加载等工作。

Spring Data JPA

Spring Data JPAJPA 之上又封装了一层 Repository。

比如定义一个接口:

1public interface UserRepository extends JpaRepository<User, Long> {
2}
3

就能直接使用:

1userRepository.save(user);
2userRepository.findById(1L);
3userRepository.findAll();
4userRepository.deleteById(1L);
5

简单理解:

1Hibernate 负责 ORM
2Spring Data JPA 负责 Repository 抽象
3Spring Boot 负责自动配置
4

Maven 依赖

Spring Boot 项目里直接引入 starter:

1<dependency>
2    <groupId>org.springframework.boot</groupId>
3    <artifactId>spring-boot-starter-data-jpa</artifactId>
4</dependency>
5

数据库驱动以 MySQL 为例:

1<dependency>
2    <groupId>com.mysql</groupId>
3    <artifactId>mysql-connector-j</artifactId>
4    <scope>runtime</scope>
5</dependency>
6

如果需要写 Web 接口:

1<dependency>
2    <groupId>org.springframework.boot</groupId>
3    <artifactId>spring-boot-starter-web</artifactId>
4</dependency>
5

Spring Boot 3 以后使用的是 jakarta.persistence 包。

也就是实体注解来自:

1import jakarta.persistence.Entity;
2import jakarta.persistence.Table;
3

不是旧的:

1import javax.persistence.Entity;
2

数据源和 JPA 配置

application.yml 示例:

1spring:
2  datasource:
3    url: jdbc:mysql://localhost:3306/jpa_demo?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
4    username: root
5    password: 123456
6    driver-class-name: com.mysql.cj.jdbc.Driver
7
8  jpa:
9    hibernate:
10      ddl-auto: update
11    show-sql: true
12    properties:
13      hibernate:
14        format_sql: true
15

常见配置说明:

配置作用
ddl-auto控制 Hibernate 是否自动处理表结构
show-sql是否打印 SQL
format_sql是否格式化 SQL

ddl-auto 常见取值:

含义常见场景
none不处理表结构生产环境
validate校验实体和表结构稳定环境
update根据实体更新表结构开发环境
create启动时删除并重建表本地临时测试
create-drop启动创建,关闭删除测试场景

生产环境通常使用:

1spring:
2  jpa:
3    hibernate:
4      ddl-auto: none
5

表结构更适合交给 FlywayLiquibase 或数据库变更流程管理。

准备演示表

下面用用户表和订单表做示例。

1DROP TABLE IF EXISTS orders;
2DROP TABLE IF EXISTS users;
3
4CREATE TABLE users (
5  id BIGINT PRIMARY KEY AUTO_INCREMENT,
6  username VARCHAR(50) NOT NULL,
7  email VARCHAR(100) NOT NULL,
8  age INT NOT NULL,
9  status VARCHAR(20) NOT NULL,
10  version INT NOT NULL DEFAULT 0,
11  created_at DATETIME NOT NULL,
12  updated_at DATETIME NULL,
13  UNIQUE KEY uk_users_email (email)
14);
15
16CREATE TABLE orders (
17  id BIGINT PRIMARY KEY AUTO_INCREMENT,
18  user_id BIGINT NOT NULL,
19  order_no VARCHAR(50) NOT NULL,
20  amount DECIMAL(10, 2) NOT NULL,
21  status VARCHAR(20) NOT NULL,
22  created_at DATETIME NOT NULL,
23  INDEX idx_orders_user_id (user_id)
24);
25
26INSERT INTO users (username, email, age, status, version, created_at, updated_at) VALUES
27('张三', 'zhangsan@example.com', 20, 'ACTIVE', 0, '2026-01-01 10:00:00', NULL),
28('李四', 'lisi@example.com', 25, 'ACTIVE', 0, '2026-01-02 10:00:00', NULL),
29('王五', 'wangwu@example.com', 17, 'DISABLED', 0, '2026-01-03 10:00:00', NULL);
30
31INSERT INTO orders (user_id, order_no, amount, status, created_at) VALUES
32(1, 'A001', 99.00, 'PAID', '2026-02-01 10:00:00'),
33(1, 'A002', 260.00, 'PAID', '2026-02-02 10:00:00'),
34(2, 'A003', 35.50, 'CANCELLED', '2026-02-03 10:00:00');
35

实体类

用户实体:

1package com.example.demo.entity;
2
3import jakarta.persistence.Column;
4import jakarta.persistence.Entity;
5import jakarta.persistence.GeneratedValue;
6import jakarta.persistence.GenerationType;
7import jakarta.persistence.Id;
8import jakarta.persistence.OneToMany;
9import jakarta.persistence.Table;
10import jakarta.persistence.Version;
11
12import java.time.LocalDateTime;
13import java.util.ArrayList;
14import java.util.List;
15
16@Entity
17@Table(name = "users")
18public class User {
19
20    @Id
21    @GeneratedValue(strategy = GenerationType.IDENTITY)
22    private Long id;
23
24    @Column(nullable = false, length = 50)
25    private String username;
26
27    @Column(nullable = false, unique = true, length = 100)
28    private String email;
29
30    @Column(nullable = false)
31    private Integer age;
32
33    @Column(nullable = false, length = 20)
34    private String status;
35
36    @Version
37    private Integer version;
38
39    @Column(name = "created_at", nullable = false)
40    private LocalDateTime createdAt;
41
42    @Column(name = "updated_at")
43    private LocalDateTime updatedAt;
44
45    @OneToMany(mappedBy = "user")
46    private List<Order> orders = new ArrayList<>();
47
48    public Long getId() {
49        return id;
50    }
51
52    public void setId(Long id) {
53        this.id = id;
54    }
55
56    public String getUsername() {
57        return username;
58    }
59
60    public void setUsername(String username) {
61        this.username = username;
62    }
63
64    public String getEmail() {
65        return email;
66    }
67
68    public void setEmail(String email) {
69        this.email = email;
70    }
71
72    public Integer getAge() {
73        return age;
74    }
75
76    public void setAge(Integer age) {
77        this.age = age;
78    }
79
80    public String getStatus() {
81        return status;
82    }
83
84    public void setStatus(String status) {
85        this.status = status;
86    }
87
88    public Integer getVersion() {
89        return version;
90    }
91
92    public void setVersion(Integer version) {
93        this.version = version;
94    }
95
96    public LocalDateTime getCreatedAt() {
97        return createdAt;
98    }
99
100    public void setCreatedAt(LocalDateTime createdAt) {
101        this.createdAt = createdAt;
102    }
103
104    public LocalDateTime getUpdatedAt() {
105        return updatedAt;
106    }
107
108    public void setUpdatedAt(LocalDateTime updatedAt) {
109        this.updatedAt = updatedAt;
110    }
111
112    public List<Order> getOrders() {
113        return orders;
114    }
115
116    public void setOrders(List<Order> orders) {
117        this.orders = orders;
118    }
119}
120

订单实体:

1package com.example.demo.entity;
2
3import jakarta.persistence.Column;
4import jakarta.persistence.Entity;
5import jakarta.persistence.FetchType;
6import jakarta.persistence.GeneratedValue;
7import jakarta.persistence.GenerationType;
8import jakarta.persistence.Id;
9import jakarta.persistence.JoinColumn;
10import jakarta.persistence.ManyToOne;
11import jakarta.persistence.Table;
12
13import java.math.BigDecimal;
14import java.time.LocalDateTime;
15
16@Entity
17@Table(name = "orders")
18public class Order {
19
20    @Id
21    @GeneratedValue(strategy = GenerationType.IDENTITY)
22    private Long id;
23
24    @ManyToOne(fetch = FetchType.LAZY)
25    @JoinColumn(name = "user_id", nullable = false)
26    private User user;
27
28    @Column(name = "order_no", nullable = false, length = 50)
29    private String orderNo;
30
31    @Column(nullable = false)
32    private BigDecimal amount;
33
34    @Column(nullable = false, length = 20)
35    private String status;
36
37    @Column(name = "created_at", nullable = false)
38    private LocalDateTime createdAt;
39
40    // getter setter
41}
42

几个重点:

  • @Entity:标记 JPA 实体
  • @Table(name = "users"):指定表名
  • @Id:主键字段
  • @GeneratedValue(strategy = GenerationType.IDENTITY):数据库自增主键
  • @Column:指定字段名、长度、是否可空等
  • @Version:乐观锁版本字段
  • @OneToMany@ManyToOne:实体关联关系

Repository 接口

1package com.example.demo.repository;
2
3import com.example.demo.entity.User;
4import org.springframework.data.jpa.repository.JpaRepository;
5import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
6
7import java.util.Optional;
8
9public interface UserRepository extends JpaRepository<User, Long>,
10        JpaSpecificationExecutor<User> {
11
12    Optional<User> findByEmail(String email);
13}
14

JpaRepository<User, Long> 里的两个泛型含义:

1User:实体类型
2Long:主键类型
3

继承后可以直接使用:

1save
2findById
3findAll
4deleteById
5count
6existsById
7

JpaSpecificationExecutor<User> 用来支持动态条件查询。

新增数据

1User user = new User();
2user.setUsername("赵六");
3user.setEmail("zhaoliu@example.com");
4user.setAge(28);
5user.setStatus("ACTIVE");
6user.setCreatedAt(LocalDateTime.now());
7
8User saved = userRepository.save(user);
9
10System.out.println(saved.getId());
11

save 可以用于新增,也可以用于更新。

判断标准大致是:

1实体没有主键,通常执行 insert
2实体有主键,通常执行 update  merge
3

实际 SQL 会由 Hibernate 根据实体状态生成。

根据 ID 查询

1Optional<User> user = userRepository.findById(1L);
2

返回 Optional<User>,表示数据可能存在,也可能不存在。

业务层可以这样处理:

1User user = userRepository.findById(id)
2        .orElseThrow(() -> new IllegalArgumentException("用户不存在"));
3

查询全部

1List<User> users = userRepository.findAll();
2

业务表数据量较大时,findAll() 要谨慎使用。

更常见的是分页查询。

删除数据

按 ID 删除:

1userRepository.deleteById(1L);
2

按实体删除:

1userRepository.delete(user);
2

删除操作通常放在事务里。

JpaRepository 常用方法

方法作用
save(entity)新增或更新
saveAll(entities)批量保存
findById(id)按 ID 查询
findAll()查询全部
findAllById(ids)按 ID 集合查询
deleteById(id)按 ID 删除
delete(entity)按实体删除
existsById(id)判断 ID 是否存在
count()统计总数
findAll(Pageable)分页查询
findAll(Sort)排序查询

方法名查询

Spring Data JPA 支持按方法名生成查询。

比如:

1Optional<User> findByEmail(String email);
2

会根据方法名生成类似查询:

1where email = ?
2

常见写法:

1List<User> findByStatus(String status);
2
3List<User> findByAgeGreaterThan(Integer age);
4
5List<User> findByUsernameContaining(String keyword);
6
7List<User> findByStatusAndAgeGreaterThan(String status, Integer age);
8
9List<User> findByStatusOrderByIdDesc(String status);
10
11boolean existsByEmail(String email);
12
13long countByStatus(String status);
14

常见关键词:

关键词示例含义
AndfindByStatusAndAge并且
OrfindByStatusOrAge或者
BetweenfindByAgeBetween区间
LessThanfindByAgeLessThan小于
GreaterThanfindByAgeGreaterThan大于
ContainingfindByUsernameContaining包含,通常是 %keyword%
StartingWithfindByUsernameStartingWith前缀匹配
EndingWithfindByUsernameEndingWith后缀匹配
InfindByIdInIN 查询
OrderByfindByStatusOrderByIdDesc排序

方法名查询适合简单条件。

如果方法名变得很长,通常应该换成 @QuerySpecification

分页和排序

分页使用 Pageable

1Pageable pageable = PageRequest.of(
2        0,
3        10,
4        Sort.by(Sort.Direction.DESC, "id")
5);
6
7Page<User> page = userRepository.findAll(pageable);
8

注意页码从 0 开始。

1 1 页:0
2 2 页:1
3 3 页:2
4

Page<User> 常用方法:

1List<User> content = page.getContent();
2long totalElements = page.getTotalElements();
3int totalPages = page.getTotalPages();
4int number = page.getNumber();
5int size = page.getSize();
6boolean hasNext = page.hasNext();
7

如果只需要当前页数据,不需要总数,可以使用 Slice

1Slice<User> findByStatus(String status, Pageable pageable);
2

Page 会查询总数。

Slice 通常只关心是否还有下一页。

@Query 自定义 JPQL

方法名查询不适合复杂条件时,可以使用 @Query

JPQL 面向实体和属性,不是直接面向表和字段。

1@Query("""
2        select u
3        from User u
4        where u.status = :status
5          and u.age >= :minAge
6        order by u.id desc
7        """)
8List<User> findActiveUsers(@Param("status") String status,
9                           @Param("minAge") Integer minAge);
10

这里的 User 是实体类名。

statusageid 是实体属性名。

不是数据库表名和字段名。

@Query 原生 SQL

如果需要直接写数据库 SQL,可以设置 nativeQuery = true

1@Query(value = """
2        select *
3        from users
4        where status = :status
5          and age >= :minAge
6        order by id desc
7        """, nativeQuery = true)
8List<User> findByNativeSql(@Param("status") String status,
9                           @Param("minAge") Integer minAge);
10

原生 SQL 适合:

  • 数据库特有语法
  • 复杂报表
  • 性能调优 SQL
  • 很难用 JPQL 表达的查询

修改查询

updatedelete 这类修改语句需要 @Modifying

1@Modifying
2@Query("""
3        update User u
4        set u.status = :status,
5            u.updatedAt = :updatedAt
6        where u.id = :id
7        """)
8int updateStatus(@Param("id") Long id,
9                 @Param("status") String status,
10                 @Param("updatedAt") LocalDateTime updatedAt);
11

调用这类方法时,需要事务。

通常放在 Service 层:

1@Transactional
2public void disable(Long id) {
3    userRepository.updateStatus(id, "DISABLED", LocalDateTime.now());
4}
5

Specification 动态查询

多条件筛选时,Specification 很常用。

Repository 需要继承:

1public interface UserRepository extends JpaRepository<User, Long>,
2        JpaSpecificationExecutor<User> {
3}
4

动态条件示例:

1public Specification<User> buildSpec(String keyword, String status, Integer minAge) {
2    return (root, query, criteriaBuilder) -> {
3        List<Predicate> predicates = new ArrayList<>();
4
5        if (keyword != null && !keyword.isBlank()) {
6            Predicate usernameLike = criteriaBuilder.like(
7                    root.get("username"),
8                    "%" + keyword + "%"
9            );
10            Predicate emailLike = criteriaBuilder.like(
11                    root.get("email"),
12                    "%" + keyword + "%"
13            );
14            predicates.add(criteriaBuilder.or(usernameLike, emailLike));
15        }
16
17        if (status != null && !status.isBlank()) {
18            predicates.add(criteriaBuilder.equal(root.get("status"), status));
19        }
20
21        if (minAge != null) {
22            predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("age"), minAge));
23        }
24
25        return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
26    };
27}
28

调用:

1Specification<User> spec = buildSpec(keyword, status, minAge);
2Pageable pageable = PageRequest.of(0, 10, Sort.by("id").descending());
3
4Page<User> page = userRepository.findAll(spec, pageable);
5

Specification 适合:

1查询条件很多
2每个条件都可选
3列表页筛选项较多
4需要复用条件片段
5

Projection 投影

有些接口只需要返回部分字段。

可以使用接口投影:

1public interface UserSummary {
2    Long getId();
3    String getUsername();
4    String getEmail();
5}
6

Repository:

1List<UserSummary> findByStatus(String status);
2

这样接口返回的不是完整 User 实体,而是只包含部分字段的视图。

也可以使用 JPQL 构造 DTO。

DTO:

1package com.example.demo.dto;
2
3public class UserSummaryDTO {
4    private Long id;
5    private String username;
6    private String email;
7
8    public UserSummaryDTO(Long id, String username, String email) {
9        this.id = id;
10        this.username = username;
11        this.email = email;
12    }
13
14    // getter
15}
16

Repository:

1@Query("""
2        select new com.example.demo.dto.UserSummaryDTO(u.id, u.username, u.email)
3        from User u
4        where u.status = :status
5        """)
6List<UserSummaryDTO> findSummaryByStatus(@Param("status") String status);
7

投影适合:

  • 列表页
  • 下拉选项
  • 只读接口
  • 不希望暴露完整实体的接口

审计字段

常见审计字段:

1created_at
2updated_at
3created_by
4updated_by
5

Spring Data JPA 可以自动填充创建时间和更新时间。

实体类先加监听器:

1import org.springframework.data.annotation.CreatedDate;
2import org.springframework.data.annotation.LastModifiedDate;
3import org.springframework.data.jpa.domain.support.AuditingEntityListener;
4
5import jakarta.persistence.EntityListeners;
6import jakarta.persistence.MappedSuperclass;
7import java.time.LocalDateTime;
8
9@MappedSuperclass
10@EntityListeners(AuditingEntityListener.class)
11public abstract class BaseEntity {
12
13    @CreatedDate
14    @Column(name = "created_at", nullable = false, updatable = false)
15    private LocalDateTime createdAt;
16
17    @LastModifiedDate
18    @Column(name = "updated_at")
19    private LocalDateTime updatedAt;
20
21    public LocalDateTime getCreatedAt() {
22        return createdAt;
23    }
24
25    public LocalDateTime getUpdatedAt() {
26        return updatedAt;
27    }
28}
29

启动类启用审计:

1import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
2
3@EnableJpaAuditing
4@SpringBootApplication
5public class JpaDemoApplication {
6}
7

实体继承:

1public class User extends BaseEntity {
2}
3

这样保存和更新时,时间字段会自动处理。

乐观锁

JPA 支持乐观锁。

实体字段:

1@Version
2private Integer version;
3

更新时会带上版本条件。

大致逻辑:

1where id = ? and version = ?
2

更新成功后版本号增加。

如果版本不一致,会抛出乐观锁相关异常。

这种机制适合防止并发修改覆盖。

关联关系和懒加载

实体关联常见有:

  • @OneToOne
  • @OneToMany
  • @ManyToOne
  • @ManyToMany

订单和用户的关系通常是:

1多个订单属于一个用户。
2

订单实体:

1@ManyToOne(fetch = FetchType.LAZY)
2@JoinColumn(name = "user_id", nullable = false)
3private User user;
4

FetchType.LAZY 表示懒加载。

只有访问 order.getUser() 时,才加载用户。

懒加载要注意事务边界。

如果事务已经关闭,再访问懒加载属性,可能出现懒加载异常。

EntityGraph

EntityGraph 可以在查询时指定需要一起加载的关联对象。

Repository:

1@EntityGraph(attributePaths = "orders")
2Optional<User> findWithOrdersById(Long id);
3

查询用户时同时加载订单。

这类写法常用于处理 N + 1 查询问题。

所谓 N + 1,大致是:

1先查 1 次用户列表
2再为每个用户各查 1 次订单
3

如果用户有 100 条,就可能变成 101 次查询。

可以用 EntityGraphfetch join、DTO 查询等方式处理。

Service 实战

1package com.example.demo.service;
2
3import com.example.demo.dto.UserSummaryDTO;
4import com.example.demo.entity.User;
5import com.example.demo.repository.UserRepository;
6import jakarta.persistence.EntityNotFoundException;
7import jakarta.persistence.criteria.Predicate;
8import org.springframework.data.domain.Page;
9import org.springframework.data.domain.PageRequest;
10import org.springframework.data.domain.Pageable;
11import org.springframework.data.domain.Sort;
12import org.springframework.data.jpa.domain.Specification;
13import org.springframework.stereotype.Service;
14import org.springframework.transaction.annotation.Transactional;
15
16import java.time.LocalDateTime;
17import java.util.ArrayList;
18import java.util.List;
19
20@Service
21@Transactional(readOnly = true)
22public class UserService {
23
24    private final UserRepository userRepository;
25
26    public UserService(UserRepository userRepository) {
27        this.userRepository = userRepository;
28    }
29
30    @Transactional
31    public Long create(User user) {
32        user.setStatus("ACTIVE");
33        user.setCreatedAt(LocalDateTime.now());
34
35        User saved = userRepository.save(user);
36
37        return saved.getId();
38    }
39
40    public User detail(Long id) {
41        return userRepository.findById(id)
42                .orElseThrow(() -> new EntityNotFoundException("用户不存在"));
43    }
44
45    public Page<User> page(String keyword, String status, Integer minAge, int pageNumber, int pageSize) {
46        Specification<User> spec = buildSpec(keyword, status, minAge);
47
48        Pageable pageable = PageRequest.of(
49                pageNumber - 1,
50                pageSize,
51                Sort.by(Sort.Direction.DESC, "id")
52        );
53
54        return userRepository.findAll(spec, pageable);
55    }
56
57    @Transactional
58    public void updateEmail(Long id, String email) {
59        User user = detail(id);
60        user.setEmail(email);
61        user.setUpdatedAt(LocalDateTime.now());
62    }
63
64    @Transactional
65    public void disable(Long id) {
66        userRepository.updateStatus(id, "DISABLED", LocalDateTime.now());
67    }
68
69    @Transactional
70    public void remove(Long id) {
71        userRepository.deleteById(id);
72    }
73
74    private Specification<User> buildSpec(String keyword, String status, Integer minAge) {
75        return (root, query, criteriaBuilder) -> {
76            List<Predicate> predicates = new ArrayList<>();
77
78            if (keyword != null && !keyword.isBlank()) {
79                Predicate usernameLike = criteriaBuilder.like(root.get("username"), "%" + keyword + "%");
80                Predicate emailLike = criteriaBuilder.like(root.get("email"), "%" + keyword + "%");
81                predicates.add(criteriaBuilder.or(usernameLike, emailLike));
82            }
83
84            if (status != null && !status.isBlank()) {
85                predicates.add(criteriaBuilder.equal(root.get("status"), status));
86            }
87
88            if (minAge != null) {
89                predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("age"), minAge));
90            }
91
92            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
93        };
94    }
95}
96

这里有一个 JPA 常见点:

1User user = detail(id);
2user.setEmail(email);
3

在事务里查出来的实体是托管状态。

修改属性后,不一定需要显式调用 save

事务提交时,Hibernate 会做脏检查并生成更新 SQL。

Controller 示例

1package com.example.demo.controller;
2
3import com.example.demo.entity.User;
4import com.example.demo.service.UserService;
5import org.springframework.data.domain.Page;
6import org.springframework.web.bind.annotation.DeleteMapping;
7import org.springframework.web.bind.annotation.GetMapping;
8import org.springframework.web.bind.annotation.PathVariable;
9import org.springframework.web.bind.annotation.PostMapping;
10import org.springframework.web.bind.annotation.PutMapping;
11import org.springframework.web.bind.annotation.RequestBody;
12import org.springframework.web.bind.annotation.RequestMapping;
13import org.springframework.web.bind.annotation.RequestParam;
14import org.springframework.web.bind.annotation.RestController;
15
16@RestController
17@RequestMapping("/users")
18public class UserController {
19
20    private final UserService userService;
21
22    public UserController(UserService userService) {
23        this.userService = userService;
24    }
25
26    @PostMapping
27    public Long create(@RequestBody User user) {
28        return userService.create(user);
29    }
30
31    @GetMapping("/{id}")
32    public User detail(@PathVariable Long id) {
33        return userService.detail(id);
34    }
35
36    @GetMapping
37    public Page<User> page(@RequestParam(required = false) String keyword,
38                           @RequestParam(required = false) String status,
39                           @RequestParam(required = false) Integer minAge,
40                           @RequestParam(defaultValue = "1") int pageNumber,
41                           @RequestParam(defaultValue = "10") int pageSize) {
42        return userService.page(keyword, status, minAge, pageNumber, pageSize);
43    }
44
45    @PutMapping("/{id}/email")
46    public void updateEmail(@PathVariable Long id, @RequestParam String email) {
47        userService.updateEmail(id, email);
48    }
49
50    @PutMapping("/{id}/disable")
51    public void disable(@PathVariable Long id) {
52        userService.disable(id);
53    }
54
55    @DeleteMapping("/{id}")
56    public void remove(@PathVariable Long id) {
57        userService.remove(id);
58    }
59}
60

实际接口不一定直接返回实体。

更常见的是返回 DTO,避免把关联字段、内部字段、懒加载对象直接暴露出去。

事务边界

JPA 非常依赖事务边界。

常见写法:

1@Service
2@Transactional(readOnly = true)
3public class UserService {
4
5    @Transactional
6    public Long create(User user) {
7        return userRepository.save(user).getId();
8    }
9}
10

查询方法默认只读事务。

写入方法单独加普通事务。

这样做的好处:

  • 查询语义更清楚
  • 写操作有事务保护
  • 懒加载和脏检查行为更稳定

Spring Data JPA 和 MyBatis 的区别

对比项Spring Data JPAMyBatis
思路面向对象和实体关系面向 SQL
常规 CRUDRepository 自动提供通常手写 SQL
动态查询方法名、Specification、CriteriaXML 动态 SQL
复杂 SQLJPQL / 原生 SQLXML SQL 更直接
关联关系实体注解表达SQL join + ResultMap
性能控制关注懒加载、N+1、事务关注 SQL 和执行计划
适合场景领域模型清楚、CRUD 多SQL 复杂、报表多

简单理解:

1Spring Data JPA 更适合实体关系清楚的业务系统
2MyBatis 更适合 SQL 控制要求高的系统
3

常见使用建议

生产环境谨慎使用 ddl-auto update

ddl-auto: update 很适合本地开发。

生产环境里,表结构变更通常需要审批、回滚方案和版本记录。

更常见的是:

1spring:
2  jpa:
3    hibernate:
4      ddl-auto: validate
5

或者:

1spring:
2  jpa:
3    hibernate:
4      ddl-auto: none
5

表结构交给数据库迁移工具。

Repository 方法名不要过长

方法名查询很方便。

比如:

1findByStatusAndAgeGreaterThan
2

但如果变成:

1findByStatusAndAgeGreaterThanAndEmailContainingAndUsernameStartingWithOrderByCreatedAtDesc
2

可读性会下降。

这时更适合使用:

  • @Query
  • Specification
  • 自定义 Repository 实现

控制实体返回范围

实体类通常包含很多数据库字段和关联关系。

接口层直接返回实体,可能带来:

  • 字段暴露过多
  • 懒加载触发异常
  • JSON 循环引用
  • 不必要的关联查询

更常见的方式是返回 DTO 或 Projection。

关联查询注意 N + 1

JPA 关联映射很方便,但也容易触发额外查询。

列表页如果要展示关联字段,可以优先考虑:

  • @EntityGraph
  • JPQL fetch join
  • DTO 查询
  • Projection

复杂报表优先考虑原生 SQL

Spring Data JPA 很适合常规业务数据访问。

但复杂报表、窗口函数、数据库特有语法、大量聚合统计,原生 SQL 通常更清楚。

可以使用:

1@Query(nativeQuery = true)
2

或者把复杂查询放到 MyBatis、JdbcTemplate、jOOQ 等更直接的 SQL 工具里。

常用注解汇总

注解作用
@Entity标记 JPA 实体
@Table指定表名
@Id指定主键
@GeneratedValue指定主键生成策略
@Column指定字段映射
@Version乐观锁版本字段
@OneToMany一对多关系
@ManyToOne多对一关系
@JoinColumn指定外键字段
@Query自定义 JPQL 或原生 SQL
@Modifying标记更新或删除语句
@EntityGraph指定查询时加载的关联属性
@CreatedDate自动填充创建时间
@LastModifiedDate自动填充更新时间
@EnableJpaAuditing启用审计

常用接口和类汇总

名称作用
JpaRepository<T, ID>通用 Repository
JpaSpecificationExecutor<T>Specification 动态查询
Pageable分页参数
PageRequest创建分页参数
Page<T>分页结果,带总数
Slice<T>分页切片,不一定查总数
Sort排序参数
Specification<T>动态条件
EntityManagerJPA 原生入口

总结

Spring Data JPA 的重点是用 Repository 和实体关系来组织数据访问。

常见开发流程是:

1建表
2 Entity
3 Repository
4 Service
5 Controller
6根据复杂度选择方法名查询、@Query、Specification、Projection
7

适合它的场景:

  • 实体关系清楚
  • 标准 CRUD 多
  • 分页列表多
  • 业务更关注对象模型
  • SQL 不需要处处手写控制

需要重点关注的地方:

  • 事务边界
  • 懒加载
  • N + 1 查询
  • DTO 和 Projection
  • ddl-auto 的环境差异
  • 复杂 SQL 的处理边界

掌握这些内容后,Spring Data JPA 已经可以覆盖大多数常规业务系统的数据访问层开发。


Java Spring Data JPA 实战指南:Repository 查询、分页与实体映射》 是转载文章,点击查看原文


相关推荐


阿里云ECS部署YOLO教程
MR_Colorful2026/6/2

1、阿里云注册 在官网注册账号:阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台 2、ECS配置选择 3、在阿里云 Workbench里为Ubuntu 18/20/22/24安装XFCE桌面(不推荐在这个里面使用,不好用!) stesteps1、通过VNC连接实例 step2、更新软件包列表和已安装的包 sudo apt update && sudo apt upgrade -y step3、安装XFCE桌面环境 sudo apt install -y xfce4 xfc


HarmonyOS 鸿蒙PC平台三方库移植:使用 vcpkg 移植 libzen(ZenLib)
展菲2026/5/25

网罗开发 (小红书、快手、视频号同名)   大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。 图书作者:《ESP32-C3 物联网工程开发实战》 图书作者:《SwiftUI 入门,进阶与实战》 超级个体:COC上海社区主理人 特约讲师:大学讲师,谷歌亚马逊分享嘉宾 科技


决定命运的,从来不是市场,而是你看待市场的方式
怕浪猫2026/5/4

真正拉开差距的,从来不是信息,而是认知。 在金融市场中,绝大多数人输掉的,不是技术,而是思维方式。 真正站在顶层的投资者,往往遵循一些看似简单、却极难长期执行的原则。这些原则,构成了投资世界的底层逻辑。 以下 9 种投资思维,不仅被反复验证,更塑造了历史上最成功的一批投资者。 一、长期主义:真正的财富,是"熬"出来的 "如果你不打算持有一只股票10年,那就不要持有10分钟。" —— Warren Buffett 真实故事:可口可乐的"慢性暴利" 1988年,巴菲特开始大量买入 Coca-


S10-蓝桥杯 17822 乐乐的积木塔
郑恩赐2026/4/25

S10-蓝桥杯 17822 乐乐的积木塔 摘要:本文详细解析蓝桥杯17822题"乐乐的积木塔",重点剖析题目中"连续递减积木塔"等绕口表述的真实含义,分享个人从困惑到理解的解题历程,并提供简洁的Python题解。 1. 题目描述 📋 1.1 问题描述 在乐乐的玩具箱中,有一系列的积木,每个积木上都标记有一个高度值。乐乐想要找出每个积木能够成为最高的连续递减积木塔的一部分的最大高度。对于每个积木,求出包含该积木的最长递减积木序列的长度。 1.2 输入格式 第一行包含一个整数 NNN。 第二行


GitHub 热榜项目 - 日榜(2026-04-14)
CoderJia_2026/4/16

GitHub 热榜项目 - 日榜(2026-04-14) 生成于:2026-04-14 统计摘要 共发现热门项目: 16 个 榜单类型:日榜 Token赞助:siliconflow 本期热点趋势总结 本期热榜彰显了 AI Agent 深度渗透垂直场景的技术趋势。Claude 生态及其相关的代码辅助开发工具(如 Claude Code 高级优化技巧、记忆插件及自动开发框架)成为绝对核心,反映出开发者对提高 LLM 编码确定性和确定化工作流的强烈需求。同时,金融领域的 Found


《从同步到消息驱动:现代后端交互模式的深度解析与工程实践》
铭渊老黄2026/4/8

《从同步到消息驱动:现代后端交互模式的深度解析与工程实践》 ——以百万行报表导出为例,谈用户体验、可观测性、失败处理与成本权衡 在过去十多年里,我见证了 Python 从“小巧优雅的脚本语言”成长为支撑全球互联网、数据科学、AI 产业的核心力量。无论是 Web 服务、自动化任务、数据处理,还是如今的 LLM 应用,Python 都以其灵活、可读、生态丰富的特性成为开发者的首选。 而在所有后端系统中,一个绕不开的问题是: “系统应该如何与用户交互?” 是同步返回?异步任务?还是彻底消息驱动? 这篇


腾讯这只小程序Agent🦀,帮我找到了最强日程、文件、知识管理姿势
AI袋鼠帝2026/3/31

大家好,我是袋鼠帝。 这两天,我发现微信里悄悄接入了一个全新的Agent入口:WorkBuddy 微信小程序。 通过这个小程序,你不仅能在云端直接召唤龙虾,甚至还能把你办公桌上那台电脑里的本地WorkBuddy,直接拉进微信里干活! 简单来说,就是你可以通过workbuddy小程序,操控你本地电脑上的workbuddy,进而控制你的电脑干活~ 更重要的是,进一步打通了微信的生态,用起来更舒服了。 经常看我文章的朋友对 WorkBuddy 应该不陌生。之前我写过一篇关于它的测评,我也是没想到,阅


GPU 编号进阶:CUDA\_VISIBLE\_DEVICES、多进程与容器化陷阱
吴佳浩2026/3/22

GPU 编号进阶:CUDA_VISIBLE_DEVICES、多进程与容器化陷阱 本篇是系列第二篇。第一篇介绍了 CUDA_DEVICE_ORDER 的基本概念与修复方法。本篇在此基础上,深入讲解 CUDA_VISIBLE_DEVICES 与 CUDA_DEVICE_ORDER 的叠加效应,分析多进程训练框架(torch.distributed、DeepSpeed)中的潜在陷阱,并介绍 Docker 和 Kubernetes 环境下的特殊处理方式,最后给出混合 GPU 环境下的正确架构设计思路。


PostgreSQL 入门学习教程,从入门到精通,PostgreSQL 16 服务器配置与数据库监控终极指南 —语法、案例与实战(18)
知识分享小能手2026/3/14

PostgreSQL 16 服务器配置与数据库监控终极指南 —语法、案例与实战 ✅ 一、服务器配置概述 PostgreSQL 16 的服务器配置主要通过 配置文件 和 SQL 命令 控制,涵盖连接、资源、日志、查询优化、统计收集等核心模块。 📁 主要配置文件: postgresql.conf:主配置文件(全局参数)pg_hba.conf:客户端认证配置(Host-Based Authentication)pg_ident.conf:用户映射配置(可选) ⚙️ 配置方式优先级: 会话级设


分享被迫变直播:AI·Spring养虾记就这样上线了
飞哥数智谈2026/3/6

今天在我的个人公众号上做了自己社群 AI·Spring 的第一次线上分享直播——养虾记第一期。 对,你没看错,社群的第一次直播是在个人号上。 本来想着第一次分享,直接走内部分享的,但昨天晚上忽然想到腾讯会议、飞书会议都有人数、时间限额,大晚上的把我搞清醒了,我又加班尝试的视频号直播。 可社群视频号还没下来,只能临时使用个人视频号直播了,于是,就这样赶鸭上架了。 腾讯会议2人不限时,超过2人40分钟,飞书会议个人版最多25人,最多45分钟,其实可以和嘉宾用腾讯会议的2人模式的,当时有点懵 AI

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读