背景
本文是《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 的痛点:
- 大量重复的样板代码(获取连接、创建语句、处理结果集、关闭资源);
- SQL 与 Java 代码混合,维护困难;
- 结果集到 Java 对象的转换需要手动编写映射逻辑;
- 表结构变更时,所有相关的 SQL 和映射代码都要改。
ORM 带来的好处:
- 开发者操作对象,框架自动生成 SQL;
- 对象与表自动映射,无需手动转换;
- 代码更面向对象,更易理解和维护;
- 数据库切换时,只需改配置,业务代码不变。
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。
解决方案:
- 在事务内提前访问(触发加载);
- 使用
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. 关键理解
- JDBC 是基础:所有 ORM 框架底层都依赖 JDBC,理解 JDBC 才能理解持久化框架的原理;
- JPA 是标准,Hibernate 是实现:JPA 定义规范,Hibernate 提供实现,二者关系类似于 Servlet 规范与 Tomcat;
- 持久化上下文是核心:理解 EntityManager 和持久化上下文,就理解了 JPA 的缓存和事务机制;
- 延迟加载需谨慎:默认延迟加载提升性能,但需注意
EntityManager 关闭后的异常;
- 事务管理是关键:声明式事务(
@Transactional)比编程式事务更简洁,是生产环境首选。
3. 与 Spring Data JPA 的关系
在实际企业开发中,很少直接使用原生 JPA,而是使用 Spring Data JPA,它在 JPA 基础上提供了:
- Repository 接口,无需编写实现类;
- 方法名自动生成 SQL(如
findByUsernameAndAge());
- 分页和排序的天然支持;
- 与 Spring 生态无缝集成。
但理解原生 JPA 是掌握 Spring Data JPA 的基础——知其然,更要知其所以然。