Hibernate 映射文件(.hbm.xml)详解:从基础映射到高级配置
Hibernate 映射文件(通常以 .hbm.xml 命名,如 User.hbm.xml)是 ORM 思想的核心载体,负责定义 Java 持久化类 与 数据库表 之间的映射关系(包括类与表、属性与字段、主键生成策略等)。本文基于示例配置,从基础标签到高级特性,全面解析映射文件的核心功能与最佳实践。
映射文件基础结构
1 |
|
- DOCTYPE 约束:指定映射文件的 DTD 规范(版本 3.0),确保标签和属性合法;
- 根标签
<hibernate-mapping>:可配置全局默认值(如包名、访问策略),简化子标签配置; - 核心标签
<class>:定义单个 Java 类与数据库表的映射关系,是映射文件的核心。
核心标签详解
1. <class>:类与表的映射
<class> 标签是映射文件的核心,负责关联 Java 类和数据库表,支持多种属性控制映射行为。
(1)基础属性
| 属性名 | 作用描述 | 示例值 |
|---|---|---|
name |
Java 持久化类的全类名(若根标签指定 package,可省略包名) |
com.zhanghe.study.model.User |
table |
对应的数据库表名(默认与类名相同,建议显式指定,避免大小写问题) | t_user |
dynamic-insert |
动态生成 INSERT 语句:仅包含非 null 的属性(减少无效字段插入) |
true/false(默认 false) |
dynamic-update |
动态生成 UPDATE 语句:仅包含被修改的属性(减少 SQL 长度,提升性能) |
true/false(默认 false) |
mutable |
是否允许更新 / 删除:false 表示实例不可修改,等价于所有 <property> 的 update="false" |
true/false(默认 true) |
lazy |
是否启用延迟加载(仅对关联查询有效,如一对多关系) | true/false(默认 true) |
(2)示例:动态插入 / 更新配置
1 | <!-- 开启动态插入和更新,适合字段较多的表 --> |
- 效果:
保存用户时,若age为null,生成的INSERT语句不含age字段;更新用户时,仅修改被变更的属性(如仅修改username,则UPDATE语句仅包含user_name字段)。
2. <id>:主键映射
<id> 标签定义 Java 类的主键属性与数据库表的主键字段的映射,是 <class> 标签的必需子标签。
(1)基础属性
| 属性名 | 作用描述 | 示例值 |
|---|---|---|
name |
Java 类的主键属性名 | id |
column |
数据库表的主键字段名(默认与属性名相同) | user_id |
type |
主键数据类型(可指定 Java 类型或 Hibernate 类型,如 java.lang.Long/long) |
java.lang.Integer |
(2)<generator>:主键生成策略
<generator> 子标签指定主键的生成方式,Hibernate 提供多种内置策略,无需手动赋值。
| 策略名 | 作用描述 | 适用数据库 |
|---|---|---|
increment |
Hibernate 自增:查询表中主键最大值 + 1(非线程安全,仅适合单节点测试) | 所有数据库 |
identity |
依赖数据库自增字段(如 MySQL 的 AUTO_INCREMENT) |
MySQL、SQL Server |
sequence |
依赖数据库序列(如 Oracle 的 SEQUENCE) |
Oracle、PostgreSQL |
hilo |
高低位算法:从数据库表获取 high 值,本地生成 low 值(避免数据库交互) |
所有数据库 |
native |
自动适配:根据数据库类型选择 identity/sequence/hilo(推荐生产使用) |
跨数据库项目 |
uuid |
生成 32 位 UUID 字符串(无需数据库依赖,适合分布式场景) | 所有数据库 |
(3)示例:主键配置
1 | <id name="id" column="user_id" type="java.lang.Long"> |
3. <property>:普通属性映射
<property> 标签定义 Java 类的普通属性与数据库表的普通字段的映射,支持精细化控制字段行为。
(1)基础属性
| 属性名 | 作用描述 | 示例值 |
|---|---|---|
name |
Java 类的属性名 | username |
column |
数据库表的字段名(默认与属性名相同,支持下划线命名,如 user_name) |
user_name |
type |
属性数据类型(Java 类型或 Hibernate 类型,如 java.lang.String/string) |
string |
not-null |
是否为非空字段(生成 NOT NULL 约束) |
true/false(默认 false) |
unique |
是否为唯一字段(生成 UNIQUE 约束) |
true/false(默认 false) |
length |
字段长度(仅对字符串类型有效,如 varchar(50)) |
50 |
insert |
是否包含在 INSERT 语句中(false 表示该字段仅可读,如创建时间) |
true/false(默认 true) |
update |
是否包含在 UPDATE 语句中(false 表示该字段不可修改,如创建时间) |
true/false(默认 true) |
access |
属性访问策略(property/field) |
property(默认) |
(2)示例:普通属性配置
1 | <!-- 用户名:非空、唯一、长度50 --> |
高级配置特性
1. 属性访问策略(access 属性)
Hibernate 支持两种访问 Java 类属性的方式,通过 <property> 的 access 属性指定:
(1)access="property"(默认)
机制:通过属性的
getter/setter方法访问属性值,不直接操作成员变量;优势:符合 JavaBean 规范,可在
getter/setter中添加额外逻辑(如数据校验、格式转换);示例:
1
2
3
4
5
6
7
8
9
10public class User {
private String username;
// getter 中添加逻辑:返回小写用户名
public String getUsername() {
return username != null ? username.toLowerCase() : null;
}
public void setUsername(String username) {
this.username = username;
}
}
1 | <property name="username" access="property"/> <!-- 通过 getter/setter 访问 --> |
(2)access="field"
机制:通过 Java 反射直接访问成员变量(无需
getter/setter方法);优势:简化代码(无需编写冗余的
getter/setter),适合简单属性;示例:
1
2
3public class User {
public String username; // 可直接访问(无需 getter/setter)
}
1 | <property name="username" access="field"/> <!-- 反射直接访问字段 --> |
2. 派生属性(formula 属性)
派生属性是指值需通过 SQL 表达式计算得出的属性(如 “订单总金额 = 单价 × 数量”“用户订单数 = 关联订单表的计数”),无需在数据库中存储,通过 <property> 的 formula 属性配置。
示例:用户订单总金额(派生属性)
1 | <class name="User" table="t_user"> |
效果:查询用户时,Hibernate 会自动拼接子查询计算totalOrderAmount,生成的 SQL 如下:
1
2
3
4
5
6SELECT
u.user_id,
u.user_name,
(SELECT SUM(o.amount) FROM t_order o WHERE o.user_id = u.user_id) AS totalOrderAmount
FROM t_user u
WHERE u.user_id = 1;注意:派生属性仅支持查询(
SELECT),不支持插入 / 更新(INSERT/UPDATE),需确保insert="false"和update="false"(默认已满足)。
3. 字段插入 / 更新控制
通过 <class> 和 <property> 的属性,可精细控制字段是否参与 INSERT/UPDATE 操作,常见场景如下:
(1)仅插入不更新(如创建时间)
1 | <!-- 创建时间:插入时自动填充,后续不可修改 --> |
(2)仅更新不插入(如更新时间)
1 | <!-- 更新时间:插入时为 null,更新时自动填充 --> |
(3)动态插入 / 更新(字段较多时优化)
当表的字段较多(如 20+ 字段),且多数字段可能为 null 或无需频繁更新时,开启 dynamic-insert 和 dynamic-update 可显著优化 SQL 性能:
1 | <class name="User" table="t_user" dynamic-insert="true" dynamic-update="true"> |
- 动态插入:仅插入非
null的字段,避免INSERT语句包含大量null值; - 动态更新:仅更新被修改的字段,减少
UPDATE语句长度,降低数据库 IO 开销。
4. 缓存配置(<cache> 标签)
在 <class> 标签内配置 <cache>,可指定该实体类是否启用二级缓存,以及缓存策略:
1 | <class name="User" table="t_user"> |
usage缓存策略:| 策略名 | 作用描述 | 适用场景 |
| ——————————— | —————————————————————————————— | —————————————————— |
|read-only| 只读缓存:仅支持查询,不支持更新(适合字典表、静态数据) | 高频读、零修改的数据 |
|read-write| 读写缓存:支持查询和更新,确保缓存与数据库一致性(通过版本控制) | 高频读、低频修改的数据(如用户信息) |
|nonstrict-read-write| 非严格读写缓存:不保证实时一致性(适合允许短暂不一致的场景) | 统计数据、非核心业务数据 |
|transactional| 事务缓存:支持分布式事务,确保跨事务的缓存一致性 | JTA 分布式事务场景 |
映射文件最佳实践
- 表名 / 字段名规范:
数据库表名建议加前缀(如t_user),字段名使用下划线命名(如user_name),与 Java 类的驼峰命名(userName)对应,通过column属性显式指定,避免大小写问题。 - 主键生成策略选择:
- 单节点 MySQL/SQL Server:使用
native(依赖自增字段); - 分布式系统:使用
uuid(无数据库依赖)或自定义策略(如雪花算法); - Oracle:使用
sequence(依赖数据库序列)。
- 单节点 MySQL/SQL Server:使用
- 动态插入 / 更新的适用场景:
仅当表的字段较多(如 10+ 字段)且多数字段可能为null或无需频繁更新时,才开启dynamic-insert和dynamic-update;字段较少时,无需开启(避免 Hibernate 额外的字段判断开销)。 - 派生属性的谨慎使用:
派生属性依赖子查询,若子查询复杂(如多表联查、聚合函数),会显著降低查询性能,建议仅用于简单计算(如单表聚合),复杂场景优先在业务层处理。 - 避免冗余配置:
根标签<hibernate-mapping>可配置package属性,简化<class>标签的name属性(无需重复包名);默认属性(如access="property")可省略,减少配置冗余。
总结
Hibernate 映射文件是 ORM 映射的核心,通过 <class>、<id>、<property> 等标签,实现了 Java 类与数据库表的 “无缝对接”。掌握基础映射(类、主键、普通属性)和高级特性(动态 SQL、派生属性、缓存),能帮助开发者灵活应对不同业务场景:
- 简单表:使用基础配置,保证映射正确性;
- 复杂表(多字段、高频查询):启用动态 SQL 和二级缓存,优化性能;
- 特殊字段(创建时间、派生属性):通过
insert/update和formula实现精细化控制
v1.3.10