当前位置:首页 > 技术 > 正文内容

Hibernate一对多关联映射与级联控制实战

访客 技术 2026年10月17日 1

实体模型与字段约束

在持久层设计中,一对多(One-to-Many)关联通常对应业务中的"分类"与"成员"关系。以商品库存管理与类目划分场景为例,构建实体时必须遵守以下面向对象建模规范:

  1. 主表实体(一方):代表分类维度。内部需声明一个私有的多端实体引用类型属性,用于反向定位所属类别,同时暴露标准的访问器。
public class Product {
    private Integer productId;
    private String productName;
    private String productDescription;
    
    /**
     * 一方实体持有对多端实体的引用指针
     */
    private Category category;
    
    public Category getCategory() { return category; }
    public void setCategory(Category category) { this.category = category; }
}
  1. 从表实体(多方):代表具体条目。需维护一个集合类型属性以容纳所有隶属于该分类的对象,确保双向导航的数据完整性。
public class Category {
    private Integer categoryId;
    private String categoryName;
    private String categoryRemark;
    
    /**
     * 多端实体使用集合容器缓存关联的一方对象列表
     */
    private Set<Product> productItems = new HashSet<>();
    
    public Set<Product> getProductItems() { return productItems; }
    public void setProductItems(Set<Product> productItems) { this.productItems = productItems; }
}

HBM映射文件定义

Hibernate的XML映射机制通过特定标签显式声明表间外键依赖。双方映射文件需协同配置以保证元数据一致性。

一方映射配置

在一方的HBM文件中,采用<many-to-one>元素建立指向主表的连接。需注意指定当前实体中对应的引用属性名、目标全限定类名以及物理外键列名。

<hibernate-mapping>
    <class name="com.app.model.Product" table="t_product">
        <id name="productId" column="pid">
            <generator class="native"/>
        </id>
        <property name="productName" column="pname"/>
        <property name="productDescription" column="pdesc"/>
        
        <!-- 声明多端到一方的归属关系 -->
        <many-to-one name="category" class="com.app.model.Category" column="fk_category_id" />
    </class>
</hibernate-mapping>

多方映射配置

在核心的分类表中,使用<set>标签展开一对多集合。内部必须包含<key>节点标明外键来源列,并搭配<one-to-many>指定映射的具体实体路径。

<hibernate-mapping>
    <class name="com.app.model.Category" table="t_category">
        <id name="categoryId" column="cid">
            <generator class="native"/>
        </id>
        <property name="categoryName" column="cname"/>
        <property name="categoryRemark" column="cremark"/>
        
        <!-- 声明一方对多端的集合映射 -->
        <set name="productItems" table="t_product">
            <key column="fk_category_id" />
            <one-to-many class="com.app.model.Product" />
        </set>
    </class>
</hibernate-mapping>

会话工厂与工具类封装

核心配置文件负责绑定数据库驱动、方言策略及DDL生成模式。工具类则负责初始化Configuration并暴露线程安全的Session入口。

<!-- hibernate.cfg.xml -->
<session-factory>
    <property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property>
    <property name="hibernate.connection.url">jdbc:mysql:///db_demo</property>
    <property name="hibernate.connection.username">root</property>
    <property name="hibernate.connection.password">admin</property>
    
    <property name="hibernate.show_sql">true</property>
    <property name="hibernate.format_sql">true</property>
    <property name="hibernate.hbm2ddl.auto">update</property>
    <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
    <property name="hibernate.current_session_context_class">thread</property>
    
    <mapping resource="com/app/model/Product.hbm.xml"/>
    <mapping resource="com/app/model/Category.hbm.xml"/>
</session-factory>
public class SessionManager {
    private static final SessionFactory FACTORY;
    static {
        try {
            CONFIGURATION = new Configuration().configure("hibernate.cfg.xml");
            FACTORY = CONFIGURATION.buildSessionFactory();
        } catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }
    
    public static Session getCurrent() {
        return FACTORY.getCurrentSession();
    }
}

级联保存策略演进

传统手动同步双方状态会导致冗余代码。通过开启级联属性,可大幅简化插入逻辑。

手动维护阶段

@Test
public void insertManualCascade() {
    Transaction tx = null;
    Session session = SessionManager.getCurrent();
    try {
        tx = session.beginTransaction();
        
        Category foodCat = new Category();
        foodCat.setCategoryName("生鲜食品");
        
        Product apple = new Product();
        apple.setProductName("红富士苹果");
        apple.setProductDescription("当季优选");
        
        // 双向绑定依赖链
        foodCat.getProductItems().add(apple);
        apple.setCategory(foodCat);
        
        session.save(foodCat);
        session.save(apple);
        tx.commit();
    } catch (Exception e) {
        if(tx != null) tx.rollback();
    }
}

开启级联自动传播

修改多方HBM配置,追加cascade="save-update"参数后,仅需持久化根节点即可。

<set name="productItems" cascade="save-update">
    <key column="fk_category_id" />
    <one-to-many class="com.app.model.Product" />
</set>
@Test
public void insertAutoCascade() {
    Session session = SessionManager.getCurrent();
    try (Transaction tx = session.beginTransaction()) {
        Category electronics = session.get(Category.class, 2);
        Product smartphone = new Product();
        smartphone.setProductName("智能手机");
        
        // 仅修改内存集合状态
        electronics.getProductItems().add(smartphone);
        
        // 触发级联写入底层表
        session.save(electronics);
        tx.commit();
    }
}

关联控制权优化:inverse属性

默认配置下Hibernate会在两端都执行更新外键的SQL语句,产生不必要的I/O开销。将多端映射的inverse="true"设置为真,可将关系维护权收归至一方,显著提升批量操作性能。

<set name="productItems" cascade="save-update" inverse="true">
    <key column="fk_category_id" />
    <one-to-many class="com.app.model.Product" />
</set>

级联删除与归属变更

配合不同的级联动作标识,可实现自动化清理或迁移操作。

递归清除

配置cascade="save-update,delete"后,删除父分类将连带移除下属所有商品记录。

@Test
public void cascadeRemoveHierarchy() {
    Session session = SessionManager.getCurrent();
    try (Transaction tx = session.beginTransaction()) {
        Category target = session.get(Category.class, 1);
        session.delete(target);
        tx.commit();
    }
}

动态重分配

利用双向导航特性,可快速调整对象所属的分组边界。

@Test
public void reassignProductBelonging() {
    Session session = SessionManager.getCurrent();
    try (Transaction tx = session.beginTransaction()) {
        Category newZone = session.get(Category.class, 3);
        Product movingItem = session.get(Product.class, 5);
        
        // 切换归属方向
        newZone.getProductItems().add(movingItem);
        movingItem.setCategory(newZone);
        
        tx.commit();
    }
}
数据库初始结构快照
级联插入后的数据分布
级联删除执行状态

重分配前表A状态

归属变更后最终视图
外键列实时更新确认

相关文章

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

linux screen 用法详情 (nohup 的替代方案)

一、screen 是什么?能干嘛?screen 是一个终端复用器,可以:在一个 SSH 会话中开多个“虚拟终端”SSH 断线后,程序仍然在后台运行随时重新连接到原来的会话特别适合:nohup 的替代方案跑脚本 / 爬虫 / 训练模型运维、远程开发二、安装 screen# CentOS / Rocky / Almayum install -y screen# Debian / Ubuntuapt i...

PHPStan 有什么用?怎么用?

PHPStan 是一个 PHP 的静态分析工具,在不运行代码的情况下就能帮你发现潜在问题,比如:传错类型(把 string 传给接受 int 的函数)访问不存在的属性 / 方法null 没处理好永远不会执行到的代码数组 key/值类型不一致返回值不符合声明注释和真实类型不匹配它非常适合:想提升代码质量、减少线上 bug、统一团队风格的人(尤其是中大型项目)。一、PHPStan 有什么用(通俗点说)...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。