第 10 章 · GORM 实体映射与 gorm.DB
本章目标:在 shop-db 中使用纯 GORM(不含 Spring)配置 gorm.Open;掌握 gorm.Model / @Table / @Column 映射 ch07 DDL;理解 gorm.DB 与持久化上下文(Persistence Context);完成 Category / Product 的 CRUD 与 slug + is_published + price(分) 映射;为 ch11 关系映射打基础。
学时建议:6~7 小时(含 3 小时跟练)
前置:完成 go-database ch07~ch09;了解 GORM 为 GORM 参考实现。
10.1 场景说明:ORM 替代手写 database/sql
ch08 的 ProductRepository 手写 SQL 与 mapRow。表结构稳定后,GORM 将行映射为 Go 对象,由提供者(GORM)生成 SQL。
| 层次 | 技术 | 本章 |
|---|---|---|
| SQL | ch07 DDL | 基线 |
| database/sql | ch08 QueryContext/ExecContext | 对照 |
| 连接池 | ch09 sqlx 连接池 | 数据源 |
| GORM | gorm.DB | 本章 |
| Spring Data | ch13 | 后续 |
纯 GORM:不引入spring-boot-starter-data-jpa,仅用jakarta.persistenceAPI + GORM,理解底层再学 Spring 封装。
10.2 go mod 依赖
<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>sqlx 连接池</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 | GORM 实现 |
sqlx 连接池 | 非 JTA 环境供 GORM 使用 |
hibernate-jpamodelgen | 编译期生成 Metamodel(Criteria 用,ch11) |
10.3 gorm.Open 配置
src/main/resources/META-INF/gorm.Open:
<?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.GORMPersistenceProvider</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="mysql DSNmysql://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"/>
<!-- 使用 sqlx 连接池 -->
<property name="hibernate.connection.provider_class"
value="org.hibernate.hikaricp.internal.sqlx 连接池ConnectionProvider"/>
<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 golang-migrate) |
show_sql | 打印 SQL(开发) |
10.4 gorm.DBFactory 与 gorm.DB
package com.example.shopdb.jpa;
import jakarta.persistence.gorm.DB;
import jakarta.persistence.gorm.DBFactory;
import jakarta.persistence.Persistence;
public final class JpaBootstrap {
private static final gorm.DBFactory EMF =
Persistence.creategorm.DBFactory("shop-db");
private JpaBootstrap() {}
public static gorm.DB opengorm.DB() {
return EMF.creategorm.DB();
}
public static void shutdown() {
EMF.close();
}
}
生命周期:
Persistence.creategorm.DBFactory("shop-db") // 昂贵,应用级单例
→ emf.creategorm.DB() // 轻量,每用例/线程
→ em.close() // 释放持久化上下文
emf.close() // 关闭池与缓存
| 对象 | 作用域 | 线程安全 |
|---|---|---|
gorm.DBFactory | 应用 | ✅ |
gorm.DB | 请求/用例 | ❌ 勿跨线程 |
10.5 持久化上下文(Persistence Context)
持久化上下文是 GORM 管理的一级缓存 + 变更追踪区域:
gorm.DB.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(),GORM 不知晓 |
| Managed | 在 PC 内,变更自动同步 |
| Detached | 曾管理,已脱离 EM |
| Removed | remove() 标记删除,flush 时 DELETE |
示例:脏检查
gorm.DB em = JpaBootstrap.opengorm.DB();
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 gorm.Model Category 映射
gorm.Model
@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; }
}
| 注解 | 作用 |
|---|---|
gorm.Model / @Table | 声明实体与表名 |
@GeneratedValue(IDENTITY) | MySQL AUTO_INCREMENT |
@Column | 列名、nullable、length |
10.7 gorm.Model Product 映射(slug / is_published / price 分)
gorm.Model
@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
@Column(name = "is_published", nullable = false)
private boolean published = false;
@Column(nullable = false)
private int stock = 0;
protected Product() {}
public Product(String title, String slug, long priceCents, Category category) {
this.title = title;
this.slug = slug;
this.price = priceCents;
this.category = category;
}
// getters / setters 省略
}