背景

本文是《JavaEE 后端从小白到大神》修仙系列第四篇,正式进入JavaEE后端世界。若想详细学习请点击首篇博文,我们开始吧。

第四篇:JDBC 与 JPA(持久化层)

  • JDBC 原理与连接池;
  • ORM 思想;
  • JPA 核心注解(@Entity、@Table、@Id、@OneToMany、@ManyToOne、@JoinColumn);
  • JPQL 与 Criteria API;
  • JPA 与 Hibernate 实现;
  • 二级缓存、延迟加载、事务管理;
  • EntityManager 与持久化上下文。

一、JDBC 原理与连接池

1. 什么是 JDBC

JDBC(Java Database Connectivity)是 Java 访问关系数据库的标准 API,是 Java 与数据库之间的“桥梁”。无论你用的是 MySQL、PostgreSQL、Oracle 还是 SQL Server,JDBC 提供了一套统一的接口,让 Java 代码可以用相同的方式操作不同的数据库。

一句话总结:JDBC = Java 操作数据库的统一标准接口。

2. JDBC 核心 API

核心接口/类 作用
DriverManager 注册数据库驱动,管理数据库连接
Connection 代表与数据库的物理连接会话
Statement 执行静态 SQL 语句
PreparedStatement 执行预编译 SQL 语句(防 SQL 注入,推荐使用
ResultSet 封装查询结果集

基本操作流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 1. 加载驱动(JDBC 4.0+ 自动加载,可省略)
Class.forName("com.mysql.cj.jdbc.Driver");

// 2. 获取连接
String url = "jdbc:mysql://localhost:3306/mydb";
String username = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url, username, password);

// 3. 创建预编译语句(防 SQL 注入)
String sql = "SELECT * FROM user WHERE id = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, 100);

// 4. 执行查询
ResultSet rs = ps.executeQuery();

// 5. 处理结果集
while (rs.next()) {
    int id = rs.getInt("id");
    String name = rs.getString("name");
}

// 6. 关闭资源(反向顺序)
rs.close();
ps.close();
conn.close();

3. JDBC 连接池

3.1 为什么需要连接池?

每次创建新的物理数据库连接都非常耗时(TCP 握手、认证、会话建立等)。在高并发场景下,频繁创建和关闭连接会严重拖垮性能。

连接池的本质:预先创建一组可重用的数据库连接,应用程序需要时从池中借用,用完后归还,而非真正关闭。

3.2 连接池工作原理

1
2
3
4
5
1. 服务器启动时 → 创建 N 个连接放入池中
2. 应用程序请求连接 → 从池中借出一个空闲连接
3. 应用程序使用连接 → 执行数据库操作
4. 应用程序关闭连接 → 连接归还到池中(并非真正关闭)
5. 若池中无空闲连接 → 等待或新建连接(取决于配置)

关键实现细节:连接池返回的 Connection 对象实际上是原始连接的代理对象。调用 close() 时,代理不会真正关闭物理连接,而是将原始连接放回池中,标记为空闲状态,等待下一次复用。

3.3 主流连接池实现

连接池 特点 适用场景
HikariCP 性能最强、字节码精简、Spring Boot 默认 生产环境首选
Tomcat JDBC Pool Tomcat 原生支持、配置简单 Tomcat 环境
DBCP2 Apache 出品、功能全面 传统项目
C3P0 支持连接池回收和自动测试 老旧项目(已逐渐淘汰)

HikariCP 配置示例

1
2
3
4
5
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>5.0.1</version>
</dependency>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
config.setUsername("root");
config.setPassword("123456");
config.setMaximumPoolSize(10);      // 最大连接数
config.setMinimumIdle(5);           // 最小空闲连接
config.setConnectionTimeout(30000); // 获取连接超时时间(毫秒)

HikariDataSource dataSource = new HikariDataSource(config);
Connection conn = dataSource.getConnection(); // 从池中借用连接
// ... 使用连接 ...
conn.close(); // 归还到池中

二、ORM 思想

1. 什么是 ORM

ORM(Object-Relational Mapping,对象关系映射)是一种编程思想:用 Java 对象来映射数据库中的表,让开发者可以用面向对象的方式操作数据库,而不是写繁琐的 SQL。

核心映射关系

现实世界 Java 世界 数据库世界
一类事物 类(Class) 表(Table)
一个具体事物 对象(Object) 一行记录(Row)
事物的特征 属性(Field) 列(Column)

举例

1
2
3
Java 中的 User 类  ←→  数据库中的 user 表
User 的一个实例对象  ←→  user 表中的一行数据
user.name 属性  ←→  user 表的 name 列

2. 为什么需要 ORM?

原生 JDBC 的痛点

  1. 大量重复的样板代码(获取连接、创建语句、处理结果集、关闭资源);
  2. SQL 与 Java 代码混合,维护困难;
  3. 结果集到 Java 对象的转换需要手动编写映射逻辑;
  4. 表结构变更时,所有相关的 SQL 和映射代码都要改。

ORM 带来的好处

  1. 开发者操作对象,框架自动生成 SQL;
  2. 对象与表自动映射,无需手动转换;
  3. 代码更面向对象,更易理解和维护;
  4. 数据库切换时,只需改配置,业务代码不变。

3. Java 生态中的 ORM 框架

框架 特点 定位
JPA Java EE 标准规范(接口定义) 标准
Hibernate JPA 最流行的实现,功能强大 实现
EclipseLink JPA 参考实现,GlassFish 默认 实现
MyBatis 半 ORM,SQL 可控性强 实现

三、JPA 核心注解

JPA(Java Persistence API)是 Java EE 定义的持久化标准规范。以下是最核心的注解。

1. @Entity —— 标识实体类

@Entity 用于标注一个 Java 类为 JPA 实体类,表示它将映射到数据库中的一张表。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import jakarta.persistence.Entity;
import jakarta.persistence.Id;

@Entity
public class User {
    @Id
    private Long id;
    private String username;
    private Integer age;
    // getter/setter
}

常见错误:忘记添加 @Entity 注解,导致 JPA 无法识别该类为实体。

2. @Table —— 自定义表名

当实体类名与数据库表名不一致时,使用 @Table 指定表名。

1
2
3
4
5
@Entity
@Table(name = "t_user")  // 映射到数据库的 t_user 表
public class User {
    // ...
}

3. @Id —— 标识主键

@Id 用于标记实体类中的主键属性。每个实体必须有一个主键。

1
2
3
4
5
6
@Entity
public class User {
    @Id
    private Long id;  // 作为主键
    // ...
}

4. @GeneratedValue —— 主键生成策略

策略 说明
GenerationType.AUTO 由 JPA 自动选择合适策略(默认)
GenerationType.IDENTITY 依赖数据库自增列(MySQL、SQL Server)
GenerationType.SEQUENCE 依赖数据库序列(Oracle、PostgreSQL)
GenerationType.TABLE 使用 JPA 维护的序列表(跨数据库兼容,性能较低)
GenerationType.UUID 使用 UUID 作为主键(Jakarta Persistence 3.1 新增)
1
2
3
4
5
6
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)  // MySQL 自增主键
    private Long id;
}

5. @Column —— 字段映射

当属性名与列名不一致,或需要指定列的长度、是否可为空等属性时使用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "user_name", nullable = false, length = 50)
    private String username;
    
    @Column(name = "user_age")
    private Integer age;
}

6. @Transient —— 忽略字段

标记某个属性映射到数据库表。

1
2
3
4
5
6
7
8
@Entity
public class User {
    @Id
    private Long id;
    
    @Transient
    private String tempData;  // 不会存入数据库
}

四、JPA 关系映射

1. 关系类型概述

数据库中表与表之间存在四种关系,JPA 通过对应的注解来映射:

数据库关系 JPA 注解 示例
多对一 @ManyToOne 多个员工属于一个部门
一对多 @OneToMany 一个部门有多个员工
一对一 @OneToOne 一个人对应一个身份证
多对多 @ManyToMany 一个学生可选多门课,一门课有多个学生

2. @ManyToOne + @JoinColumn(最常用)

场景:多个员工(Employee)属于同一个部门(Department),这是最典型的多对一关系。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 多方:Employee(员工)
@Entity
@Table(name = "t_employee")
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    
    // 多对一:多个员工属于一个部门
    @ManyToOne
    @JoinColumn(name = "dept_id")  // 外键列名
    private Department department;
}

// 一方:Department(部门)
@Entity
@Table(name = "t_department")
public class Department {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}

重要概念

  • @JoinColumn 指定外键列的名称;
  • @ManyToOne 关系中,“多”的一方是关系的拥有方,外键存储在“多”方的表中。

3. @OneToMany(一对多)

场景:一个部门有多个员工,使用 mappedBy 指向关系的拥有方。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Entity
@Table(name = "t_department")
public class Department {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    
    // 一对多:一个部门有多个员工
    // mappedBy 指向 Employee 中关联 Department 的属性名
    @OneToMany(mappedBy = "department", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<Employee> employees = new ArrayList<>();
}

mappedBy 的含义:告诉 JPA,“这个关系的映射已经在对方(Employee)的 department 属性上定义了,我不需要再维护外键”。

4. @ManyToMany(多对多)

场景:学生和课程,一个学生可选多门课,一门课有多个学生。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Student 实体
@Entity
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    
    @ManyToMany
    @JoinTable(
        name = "student_course",  // 中间表名
        joinColumns = @JoinColumn(name = "student_id"),
        inverseJoinColumns = @JoinColumn(name = "course_id")
    )
    private List<Course> courses = new ArrayList<>();
}

// Course 实体
@Entity
public class Course {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}

5. CascadeType —— 级联操作

级联类型 含义
CascadeType.PERSIST 保存父实体时,级联保存子实体
CascadeType.MERGE 更新父实体时,级联更新子实体
CascadeType.REMOVE 删除父实体时,级联删除子实体
CascadeType.REFRESH 刷新父实体时,级联刷新子实体
CascadeType.DETACH 分离父实体时,级联分离子实体
CascadeType.ALL 以上全部
1
2
@OneToMany(mappedBy = "department", cascade = CascadeType.ALL)
private List<Employee> employees;

五、EntityManager 与持久化上下文

1. 什么是持久化上下文

持久化上下文(Persistence Context) 是 JPA 中的核心概念,可以理解为一个临时缓存区,JPA 在其中管理所有已检索或创建的实体状态。

当通过 EntityManager 操作实体时,实体的状态变化先记录在持久化上下文中,在事务提交时再同步到数据库。

2. EntityManager 的核心方法

方法 作用
persist(Object entity) 将新实体变为托管状态(相当于 INSERT)
merge(T entity) 将游离实体合并到持久化上下文(相当于 UPDATE)
remove(Object entity) 删除实体(相当于 DELETE)
find(Class<T> entityClass, Object primaryKey) 根据主键查询实体
createQuery(String jpql) 创建 JPQL 查询
createNamedQuery(String name) 创建命名查询
flush() 手动将持久化上下文同步到数据库
clear() 清空持久化上下文

3. 实体生命周期(四个阶段)

JPA 实体有四个明确的生命周期阶段:

1
New(新建) → Managed(托管) → Removed(移除) → Detached(游离)

各阶段详解

阶段 状态 说明
New(新建) 瞬时态 对象刚被 new 创建,尚未与 EntityManager 关联,数据库中无对应记录
Managed(托管) 持久态 对象被 EntityManager 管理(通过 persist()find()),任何修改会在事务提交时自动同步到数据库
Removed(移除) 删除态 调用了 remove() 方法,事务提交时将从数据库删除
Detached(游离) 游离态 对象曾经被管理,但 EntityManager 已关闭或调用了 clear()/detach(),不再被跟踪,修改不会自动同步

示意图

1
2
3
4
5
6
7
8
9
new User()  →  New(新建)
em.persist(user)  →  Managed(托管)  ← 修改自动同步
em.remove(user)  →  Removed(移除)
事务提交  →  数据库 DELETE
em.close()  →  Detached(游离)

4. 持久化上下文的两种类型

类型 生命周期 适用场景
事务范围(Transaction-Scoped) 随 JTA 事务开始而开始,事务提交/回滚而结束 无状态会话 Bean(Stateless Session Bean)
扩展范围(Extended) EntityManager 的整个生命周期 有状态会话 Bean(Stateful Session Bean)

六、JPQL 与 Criteria API

1. JPQL(Java Persistence Query Language)

JPQL 是 JPA 定义的面向对象的查询语言,操作的是实体对象和属性,而非数据库表和列。

基本语法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 查询所有用户
String jpql = "SELECT u FROM User u";
TypedQuery<User> query = entityManager.createQuery(jpql, User.class);
List<User> users = query.getResultList();

// 带参数的查询
String jpql = "SELECT u FROM User u WHERE u.age > :minAge";
TypedQuery<User> query = entityManager.createQuery(jpql, User.class);
query.setParameter("minAge", 18);
List<User> users = query.getResultList();

// 分页查询
List<User> users = entityManager.createQuery("SELECT u FROM User u", User.class)
    .setFirstResult(0)   // 从第几条开始
    .setMaxResults(10)   // 最多取几条
    .getResultList();

// 聚合查询(返回单值)
String jpql = "SELECT COUNT(u) FROM User u";
Long count = entityManager.createQuery(jpql, Long.class).getSingleResult();

JPQL vs SQL 对比

对比项 JPQL SQL
操作对象 Java 实体类和属性 数据库表和列
示例 SELECT u FROM User u SELECT * FROM t_user
依赖 实体映射关系 数据库表结构

2. Criteria API(类型安全动态查询)

当查询条件在运行时才能确定(如多条件搜索),使用 Criteria API 可以动态构建查询,且具有编译时类型安全的优势。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 动态查询:根据条件动态拼接 WHERE
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> cq = cb.createQuery(User.class);
Root<User> user = cq.from(User.class);

// 动态添加条件
List<Predicate> predicates = new ArrayList<>();
if (username != null && !username.isEmpty()) {
    predicates.add(cb.equal(user.get("username"), username));
}
if (minAge != null) {
    predicates.add(cb.greaterThanOrEqualTo(user.get("age"), minAge));
}

cq.where(predicates.toArray(new Predicate[0]));
List<User> users = entityManager.createQuery(cq).getResultList();

JPQL vs Criteria API 选择指南

场景 推荐
查询条件固定 JPQL(更简洁)
查询条件动态(多条件搜索) Criteria API(类型安全)
复杂报表查询 JPQL(更灵活)
需要编译时检查 Criteria API

3. 命名查询(Named Query)

将 JPQL 定义在实体类上,提前编译,性能更好。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Entity
@NamedQuery(
    name = "User.findByAgeGreaterThan",
    query = "SELECT u FROM User u WHERE u.age > :age"
)
public class User {
    // ...
}

// 使用
TypedQuery<User> query = entityManager.createNamedQuery("User.findByAgeGreaterThan", User.class);
query.setParameter("age", 18);
List<User> users = query.getResultList();

七、二级缓存、延迟加载与事务管理

1. 延迟加载(Lazy Loading)

默认情况下,JPA 对关联实体采用延迟加载策略——只有在真正访问关联对象时,才去数据库查询。

1
2
3
4
5
@Entity
public class Department {
    @OneToMany(mappedBy = "department", fetch = FetchType.LAZY)  // 默认就是 LAZY
    private List<Employee> employees;
}

延迟加载的问题:当 EntityManager 已关闭后,再访问延迟加载的属性会抛出 LazyInitializationException

解决方案

  1. 在事务内提前访问(触发加载);
  2. 使用 JOIN FETCH 在查询时一次性加载;
1
2
3
4
5
// JOIN FETCH:一次性加载部门和所有员工
String jpql = "SELECT d FROM Department d JOIN FETCH d.employees WHERE d.id = :id";
Department dept = entityManager.createQuery(jpql, Department.class)
    .setParameter("id", 1L)
    .getSingleResult();

2. 一级缓存(Level 1 Cache)

每个 EntityManager 都有自己的一级缓存(也称为持久化上下文缓存),在同一个 EntityManager 中,相同的实体只会被查询一次,后续访问直接从缓存返回。

1
2
3
4
// 同一个 EntityManager 中
User u1 = entityManager.find(User.class, 1L);  // 查询数据库
User u2 = entityManager.find(User.class, 1L);  // 从一级缓存返回,不查数据库
// u1 == u2 为 true(同一个对象实例)

3. 二级缓存(Level 2 Cache)

二级缓存是跨 EntityManager 的全局缓存,所有 EntityManager 可以共享。适用于读多写少的数据。

特点

  • EntityManager 共享;
  • 需要显式开启;
  • 适用于读多写少的场景;
  • 会增加内存消耗,需合理配置缓存大小。

启用二级缓存

1
2
3
4
5
6
7
8
<!-- persistence.xml -->
<persistence-unit name="myPU">
    <properties>
        <property name="hibernate.cache.use_second_level_cache" value="true"/>
        <property name="hibernate.cache.region.factory_class" 
                  value="org.hibernate.cache.jcache.JCacheRegionFactory"/>
    </properties>
</persistence-unit>
1
2
3
4
5
@Entity
@Cacheable  // 标记该实体可被二级缓存
public class User {
    // ...
}

4. 事务管理

JPA 中的事务管理分为两种方式:

(1)声明式事务(推荐)

通过注解管理事务,代码最简洁:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Stateless
public class UserService {
    @PersistenceContext
    private EntityManager em;
    
    @Transactional  // 声明式事务
    public void createUser(User user) {
        em.persist(user);
        // 方法结束时自动提交事务
    }
}

(2)编程式事务

手动控制事务边界(在 Java SE 环境中常用):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
EntityTransaction tx = entityManager.getTransaction();
try {
    tx.begin();
    entityManager.persist(user);
    entityManager.merge(order);
    tx.commit();  // 提交事务,同步到数据库
} catch (Exception e) {
    tx.rollback();  // 回滚事务
    throw e;
}

八、实战案例:订单管理系统

1. 业务模型

实现一个简单的订单管理系统,包含三个核心实体:

  • User(用户):一个用户有多个订单(1 : N)
  • Order(订单):一个订单属于一个用户(N : 1),一个订单包含多个商品(N : N)
  • Product(商品):一个商品可属于多个订单(N : N)

2. 实体类定义

User 实体

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import jakarta.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "t_user")
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;
    
    private Integer age;
    
    // 一对多:一个用户有多个订单
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<Order> orders = new ArrayList<>();
    
    // getter/setter 省略
}

Order 实体

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import jakarta.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "t_order")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String orderNo;  // 订单号
    
    @Column(nullable = false)
    private BigDecimal totalAmount;  // 总金额
    
    private LocalDateTime createTime;
    
    private String status;  // PENDING, PAID, CANCELLED
    
    // 多对一:多个订单属于一个用户
    @ManyToOne
    @JoinColumn(name = "user_id", nullable = false)
    private User user;
    
    // 多对多:一个订单包含多个商品
    @ManyToMany
    @JoinTable(
        name = "t_order_product",
        joinColumns = @JoinColumn(name = "order_id"),
        inverseJoinColumns = @JoinColumn(name = "product_id")
    )
    private List<Product> products = new ArrayList<>();
    
    // getter/setter 省略
}

Product 实体

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import jakarta.persistence.*;
import java.math.BigDecimal;

@Entity
@Table(name = "t_product")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, length = 100)
    private String name;
    
    private BigDecimal price;
    
    private Integer stock;  // 库存
    
    // getter/setter 省略
}

3. Service 层(业务逻辑)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import jakarta.ejb.Stateless;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import java.time.LocalDateTime;
import java.util.List;

@Stateless
public class OrderService {
    
    @PersistenceContext
    private EntityManager em;
    
    // 创建订单
    @Transactional
    public Order createOrder(Long userId, List<Long> productIds) {
        // 1. 查询用户
        User user = em.find(User.class, userId);
        if (user == null) {
            throw new IllegalArgumentException("用户不存在");
        }
        
        // 2. 查询商品
        List<Product> products = em.createQuery(
            "SELECT p FROM Product p WHERE p.id IN :ids", Product.class)
            .setParameter("ids", productIds)
            .getResultList();
        
        // 3. 计算总金额
        BigDecimal total = products.stream()
            .map(Product::getPrice)
            .reduce(BigDecimal.ZERO, BigDecimal::add);
        
        // 4. 创建订单
        Order order = new Order();
        order.setOrderNo("ORD" + System.currentTimeMillis());
        order.setTotalAmount(total);
        order.setCreateTime(LocalDateTime.now());
        order.setStatus("PENDING");
        order.setUser(user);
        order.setProducts(products);
        
        // 5. 保存订单(级联保存关联实体)
        em.persist(order);
        return order;
    }
    
    // 查询用户的所有订单(使用 JOIN FETCH 避免 N+1 问题)
    public List<Order> findOrdersByUser(Long userId) {
        return em.createQuery(
            "SELECT o FROM Order o JOIN FETCH o.products WHERE o.user.id = :userId", 
            Order.class)
            .setParameter("userId", userId)
            .getResultList();
    }
    
    // 取消订单
    @Transactional
    public void cancelOrder(Long orderId) {
        Order order = em.find(Order.class, orderId);
        if (order == null) {
            throw new IllegalArgumentException("订单不存在");
        }
        if (!"PENDING".equals(order.getStatus())) {
            throw new IllegalStateException("只有待支付订单才能取消");
        }
        order.setStatus("CANCELLED");
        // 事务提交时自动 UPDATE
    }
}

4. 配置文件(persistence.xml)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence 
             https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd"
             version="3.0">
    
    <persistence-unit name="orderPU" transaction-type="JTA">
        <jta-data-source>java:jboss/datasources/MySQLDS</jta-data-source>
        
        <properties>
            <!-- 自动建表 -->
            <property name="jakarta.persistence.schema-generation.database.action" 
                      value="update"/>
            <!-- 显示 SQL -->
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
        </properties>
    </persistence-unit>
</persistence>

5. 测试代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class OrderServiceTest {
    @Inject
    private OrderService orderService;
    
    public void testCreateOrder() {
        // 创建订单
        List<Long> productIds = Arrays.asList(1L, 2L, 3L);
        Order order = orderService.createOrder(1L, productIds);
        System.out.println("订单创建成功:订单号=" + order.getOrderNo());
        
        // 查询订单
        List<Order> orders = orderService.findOrdersByUser(1L);
        orders.forEach(o -> {
            System.out.println("订单号:" + o.getOrderNo() + ",金额:" + o.getTotalAmount());
        });
    }
}

九、总结

1. 核心知识脉络

层次 技术 核心概念
底层 JDBC Connection、PreparedStatement、ResultSet
中间层 连接池 HikariCP、连接复用、性能优化
思想层 ORM 对象 ↔ 表映射,面向对象操作数据库
标准层 JPA @Entity、@Id、EntityManager、JPQL
实现层 Hibernate JPA 实现,二级缓存,延迟加载

2. 关键理解

  1. JDBC 是基础:所有 ORM 框架底层都依赖 JDBC,理解 JDBC 才能理解持久化框架的原理;
  2. JPA 是标准,Hibernate 是实现:JPA 定义规范,Hibernate 提供实现,二者关系类似于 Servlet 规范与 Tomcat;
  3. 持久化上下文是核心:理解 EntityManager 和持久化上下文,就理解了 JPA 的缓存和事务机制;
  4. 延迟加载需谨慎:默认延迟加载提升性能,但需注意 EntityManager 关闭后的异常;
  5. 事务管理是关键:声明式事务(@Transactional)比编程式事务更简洁,是生产环境首选。

3. 与 Spring Data JPA 的关系

在实际企业开发中,很少直接使用原生 JPA,而是使用 Spring Data JPA,它在 JPA 基础上提供了:

  • Repository 接口,无需编写实现类;
  • 方法名自动生成 SQL(如 findByUsernameAndAge());
  • 分页和排序的天然支持;
  • 与 Spring 生态无缝集成。

但理解原生 JPA 是掌握 Spring Data JPA 的基础——知其然,更要知其所以然