下载工作台
Java 数据库实战

JPA 实体映射与 EntityManager

试读上半部分 · 解锁后可读全文

第 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。

层次技术本章
SQLch07 DDL基线
JDBCch08 PreparedStatement对照
连接池ch09 HikariCP数据源
JPAEntityManager本章
Spring Datach13后续
纯 JPA:不引入 spring-boot-starter-data-jpa,仅用 jakarta.persistence API + 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-coreJPA 实现
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&amp;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
状态说明
Transientnew Product(),JPA 不知晓
Managed在 PC 内,变更自动同步
Detached曾管理,已脱离 EM
Removedremove() 标记删除,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

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。