第 10 章 · JPA 实体映射与 EntityManager
本章目标:在 shop-db 中使用纯 JPA(不含 Spring)配置 persistence.xml;掌握 @Entity / @Table / @Column 映射 ch07 DDL;理解 EntityManager 与持久化上下文(Persistence Context);完成 Category / Product 的 CRUD 与 slug + is_published + price(分) 映射;为 ch11 关系映射打基础。
学时建议:6~7 小时(含 3 小时跟练)
前置:完成 java-database ch07~ch09;了解 Hibernate 为 JPA 参考实现。
10.1 场景说明:ORM 替代手写 JDBC
ch08 的 ProductRepository 手写 SQL 与 mapRow。表结构稳定后,JPA 将行映射为 Java 对象,由提供者(Hibernate)生成 SQL。
| 层次 | 技术 | 本章 |
|---|---|---|
| SQL | ch07 DDL | 基线 |
| JDBC | ch08 PreparedStatement | 对照 |
| 连接池 | ch09 HikariCP | 数据源 |
| JPA | EntityManager | 本章 |
| Spring Data | ch13 | 后续 |
纯 JPA:不引入spring-boot-starter-data-jpa,仅用jakarta.persistenceAPI + Hibernate,理解底层再学 Spring 封装。
10.2 Maven 依赖
<properties>
<hibernate.version>6.4.4.Final</hibernate.version>
</properties>
<dependencies>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<version>${hibernate.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
| 依赖 | 作用 |
|---|---|
hibernate-core | JPA 实现 |
HikariCP | 非 JTA 环境供 Hibernate 使用 |
hibernate-jpamodelgen | 编译期生成 Metamodel(Criteria 用,ch11) |
10.3 persistence.xml 配置
src/main/resources/META-INF/persistence.xml:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
version="3.0">
<persistence-unit name="shop-db" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<class>com.example.shopdb.jpa.entity.Category</class>
<class>com.example.shopdb.jpa.entity.Product</class>
<properties>
<property name="jakarta.persistence.jdbc.driver"
value="com.mysql.cj.jdbc.Driver"/>
<property name="jakarta.persistence.jdbc.url"
value="jdbc:mysql://127.0.0.1:3306/shop_db?useSSL=false&serverTimezone=Asia/Shanghai"/>
<property name="jakarta.persistence.jdbc.user" value="shop_user"/>
<property name="jakarta.persistence.jdbc.password" value="shop_pass"/>
<property name="hibernate.dialect"
value="org.hibernate.dialect.MySQLDialect"/>
<property name="hibernate.hbm2ddl.auto" value="validate"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<!-- 使用 HikariCP -->
<property name="hibernate.connection.provider_class"
value="org.hibernate.hikaricp.internal.HikariCPConnectionProvider"/>
<property name="hibernate.hikari.maximumPoolSize" value="10"/>
<property name="hibernate.hikari.minimumIdle" value="2"/>
</properties>
</persistence-unit>
</persistence>
| 属性 | 说明 |
|---|---|
transaction-type=RESOURCE_LOCAL | 应用代码手动 em.getTransaction() |
hbm2ddl.auto=validate | 只校验实体与表一致,不自动建表(配合 ch12 Flyway) |
show_sql | 打印 SQL(开发) |
10.4 EntityManagerFactory 与 EntityManager
package com.example.shopdb.jpa;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;
public final class JpaBootstrap {
private static final EntityManagerFactory EMF =
Persistence.createEntityManagerFactory("shop-db");
private JpaBootstrap() {}
public static EntityManager openEntityManager() {
return EMF.createEntityManager();
}
public static void shutdown() {
EMF.close();
}
}
生命周期:
Persistence.createEntityManagerFactory("shop-db") // 昂贵,应用级单例
→ emf.createEntityManager() // 轻量,每用例/线程
→ em.close() // 释放持久化上下文
emf.close() // 关闭池与缓存
| 对象 | 作用域 | 线程安全 |
|---|---|---|
EntityManagerFactory | 应用 | ✅ |
EntityManager | 请求/用例 | ❌ 勿跨线程 |
10.5 持久化上下文(Persistence Context)
持久化上下文是 JPA 管理的一级缓存 + 变更追踪区域:
EntityManager.find(Product.class, 1L)
→ 先查 PC(持久化上下文)
→ 未命中则 SELECT,结果放入 PC,实体状态 = Managed
em.persist(newProduct)
→ 新实体进入 PC,状态 = Managed(Transient → Managed)
修改 managed 实体字段
→ 脏检查(dirty checking),flush 时自动 UPDATE
em.detach(product)
→ 移出 PC,状态 = Detached
em.close()
→ PC 销毁,Managed → Detached
| 状态 | 说明 |
|---|---|
| Transient | new Product(),JPA 不知晓 |
| Managed | 在 PC 内,变更自动同步 |
| Detached | 曾管理,已脱离 EM |
| Removed | remove() 标记删除,flush 时 DELETE |
示例:脏检查
EntityManager em = JpaBootstrap.openEntityManager();
em.getTransaction().begin();
Product p = em.find(Product.class, 1L);
p.setStock(p.getStock() - 1); // 无显式 UPDATE
em.getTransaction().commit(); // flush → UPDATE products SET stock=...
em.close();
10.6 @Entity Category 映射
@Entity
@Table(name = "categories")
public class Category {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 64) private String name;
@Column(nullable = false, unique = true, length = 64) private String slug;
@Column(name = "sort_order") private int sortOrder = 0;
protected Category() {}
public Category(String name, String slug) { this.name = name; this.slug = slug; }
}
| 注解 | 作用 |
|---|---|
@Entity / @Table | 声明实体与表名 |
@GeneratedValue(IDENTITY) | MySQL AUTO_INCREMENT |
@Column | 列名、nullable、length |
10.7 @Entity Product 映射(slug / is_published / price 分)
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "category_id", nullable = false)
private Category category;
@Column(nullable = false, length = 128)
private String title;
@Column(nullable = false, unique = true, length = 128)
private String slug;
@Column(nullable = false)
private long price; // 分,5990 = ¥59.90