0%

hibernate映射文件

Hibernate 映射文件(.hbm.xml)详解:从基础映射到高级配置

Hibernate 映射文件(通常以 .hbm.xml 命名,如 User.hbm.xml)是 ORM 思想的核心载体,负责定义 Java 持久化类数据库表 之间的映射关系(包括类与表、属性与字段、主键生成策略等)。本文基于示例配置,从基础标签到高级特性,全面解析映射文件的核心功能与最佳实践。

映射文件基础结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<!-- 根标签:配置映射文件的全局属性 -->
<hibernate-mapping
package="com.zhanghe.study.model" <!-- 可选,指定持久化类的默认包名,后续class标签可省略包名 -->
default-access="property" <!-- 可选,默认属性访问策略(property/field) -->
default-cascade="none" <!-- 可选,默认级联操作策略 -->
default-lazy="true"> <!-- 可选,默认延迟加载策略 -->

<!-- 核心标签:类与表的映射 -->
<class name="User" table="t_user">
<!-- 主键映射 -->
<id name="id" column="user_id">
<generator class="native"/> <!-- 主键生成策略 -->
</id>

<!-- 普通属性映射 -->
<property name="username" column="user_name" type="string"/>
</class>
</hibernate-mapping>
  • 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
2
3
4
<!-- 开启动态插入和更新,适合字段较多的表 -->
<class name="User" table="t_user" dynamic-insert="true" dynamic-update="true">
<!-- 主键和属性映射... -->
</class>
  • 效果
    保存用户时,若 agenull,生成的 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
2
3
4
<id name="id" column="user_id" type="java.lang.Long">
<!-- 生产环境推荐使用 native(适配数据库自增)或 uuid(分布式 ID) -->
<generator class="native"/>
</id>

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
2
3
4
5
6
7
8
9
10
11
<!-- 用户名:非空、唯一、长度50 -->
<property name="username" column="user_name" type="string" not-null="true" unique="true" length="50">
<!-- 可选:显式配置列属性(如注释) -->
<column comment="用户登录名"/>
</property>

<!-- 创建时间:仅插入,不更新(记录数据创建时间) -->
<property name="createTime" column="create_time" type="timestamp" insert="true" update="false">
<!-- 可选:默认值为当前时间 -->
<column default="CURRENT_TIMESTAMP"/>
</property>

高级配置特性

1. 属性访问策略(access 属性)

Hibernate 支持两种访问 Java 类属性的方式,通过 <property>access 属性指定:

(1)access="property"(默认)
  • 机制:通过属性的 getter/setter 方法访问属性值,不直接操作成员变量;

  • 优势:符合 JavaBean 规范,可在 getter/setter 中添加额外逻辑(如数据校验、格式转换);

  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public 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
    3
    public class User {
    public String username; // 可直接访问(无需 getter/setter)
    }
1
<property name="username" access="field"/> <!-- 反射直接访问字段 -->

2. 派生属性(formula 属性)

派生属性是指值需通过 SQL 表达式计算得出的属性(如 “订单总金额 = 单价 × 数量”“用户订单数 = 关联订单表的计数”),无需在数据库中存储,通过 <property>formula 属性配置。

示例:用户订单总金额(派生属性)
1
2
3
4
5
6
7
8
9
10
11
<class name="User" table="t_user">
<id name="id" column="user_id">
<generator class="native"/>
</id>
<!-- 派生属性:总订单金额(子查询计算) -->
<property
name="totalOrderAmount"
type="java.math.BigDecimal"
formula="(SELECT SUM(o.amount) FROM t_order o WHERE o.user_id = user_id)"
/>
</class>
  • 效果:查询用户时,Hibernate 会自动拼接子查询计算totalOrderAmount,生成的 SQL 如下:

    1
    2
    3
    4
    5
    6
    SELECT 
    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
3
4
<!-- 创建时间:插入时自动填充,后续不可修改 -->
<property name="createTime" column="create_time" type="timestamp" update="false">
<column default="CURRENT_TIMESTAMP"/> <!-- 数据库默认值为当前时间 -->
</property>
(2)仅更新不插入(如更新时间)
1
2
3
4
<!-- 更新时间:插入时为 null,更新时自动填充 -->
<property name="updateTime" column="update_time" type="timestamp" insert="false">
<column on update="CURRENT_TIMESTAMP"/> <!-- MySQL 自动更新为当前时间 -->
</property>
(3)动态插入 / 更新(字段较多时优化)

当表的字段较多(如 20+ 字段),且多数字段可能为 null 或无需频繁更新时,开启 dynamic-insertdynamic-update 可显著优化 SQL 性能:

1
2
3
<class name="User" table="t_user" dynamic-insert="true" dynamic-update="true">
<!-- 属性映射... -->
</class>
  • 动态插入:仅插入非 null 的字段,避免 INSERT 语句包含大量 null 值;
  • 动态更新:仅更新被修改的字段,减少 UPDATE 语句长度,降低数据库 IO 开销。

4. 缓存配置(<cache> 标签)

<class> 标签内配置 <cache>,可指定该实体类是否启用二级缓存,以及缓存策略:

1
2
3
4
5
<class name="User" table="t_user">
<!-- 启用二级缓存,策略为 read-write(读写缓存) -->
<cache usage="read-write"/>
<!-- 主键和属性映射... -->
</class>
  • usage 缓存策略:

    | 策略名 | 作用描述 | 适用场景 |
    | ——————————— | —————————————————————————————— | —————————————————— |
    | read-only | 只读缓存:仅支持查询,不支持更新(适合字典表、静态数据) | 高频读、零修改的数据 |
    | read-write | 读写缓存:支持查询和更新,确保缓存与数据库一致性(通过版本控制) | 高频读、低频修改的数据(如用户信息) |
    | nonstrict-read-write | 非严格读写缓存:不保证实时一致性(适合允许短暂不一致的场景) | 统计数据、非核心业务数据 |
    | transactional | 事务缓存:支持分布式事务,确保跨事务的缓存一致性 | JTA 分布式事务场景 |

映射文件最佳实践

  1. 表名 / 字段名规范
    数据库表名建议加前缀(如 t_user),字段名使用下划线命名(如 user_name),与 Java 类的驼峰命名(userName)对应,通过 column 属性显式指定,避免大小写问题。
  2. 主键生成策略选择
    • 单节点 MySQL/SQL Server:使用 native(依赖自增字段);
    • 分布式系统:使用 uuid(无数据库依赖)或自定义策略(如雪花算法);
    • Oracle:使用 sequence(依赖数据库序列)。
  3. 动态插入 / 更新的适用场景
    仅当表的字段较多(如 10+ 字段)且多数字段可能为 null 或无需频繁更新时,才开启 dynamic-insertdynamic-update;字段较少时,无需开启(避免 Hibernate 额外的字段判断开销)。
  4. 派生属性的谨慎使用
    派生属性依赖子查询,若子查询复杂(如多表联查、聚合函数),会显著降低查询性能,建议仅用于简单计算(如单表聚合),复杂场景优先在业务层处理。
  5. 避免冗余配置
    根标签 <hibernate-mapping> 可配置 package 属性,简化 <class> 标签的 name 属性(无需重复包名);默认属性(如 access="property")可省略,减少配置冗余。

总结

Hibernate 映射文件是 ORM 映射的核心,通过 <class><id><property> 等标签,实现了 Java 类与数据库表的 “无缝对接”。掌握基础映射(类、主键、普通属性)和高级特性(动态 SQL、派生属性、缓存),能帮助开发者灵活应对不同业务场景:

  • 简单表:使用基础配置,保证映射正确性;
  • 复杂表(多字段、高频查询):启用动态 SQL 和二级缓存,优化性能;
  • 特殊字段(创建时间、派生属性):通过 insert/updateformula 实现精细化控制

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10