0

0

Hibernate 自引用多对多关系映射详解

DDD

DDD

发布时间:2025-11-01 14:02:37

|

371人浏览过

|

来源于php中文网

原创

hibernate 自引用多对多关系映射详解

本教程详细阐述了如何在Hibernate中映射自引用多对多关系。通过一个具体的数据库表结构和Java实体示例,我们将学习如何利用`@ManyToMany`和`@JoinTable`注解,在同一个实体类型之间建立父子或相关联的连接,从而实现双向导航,高效管理复杂的层级或网络结构数据。

Hibernate 自引用多对多关系映射指南

软件开发中,我们经常会遇到实体与其自身存在多对多关联的情况,例如一个用户可以关注多个其他用户,或者一个任务可以有多个前置任务和多个后续任务。这种关系被称为自引用多对多关系。本教程将以一个具体的示例,详细讲解如何在Hibernate中正确地映射这种复杂的关系。

数据库结构概述

假设我们有一个 test_table 用于存储基础实体信息,以及一个 relation 表来维护 test_table 实体之间的多对多关系。

test_table 表结构:

列名 类型 描述
id BIGINT 主键,自动增长,不可为空
comment VARCHAR(255) 实体描述

relation 表结构:

这个表是实现自引用多对多关系的关键,它充当了 test_table 自身的连接表。

列名 类型 描述
id BIGINT 主键,自动增长
a_id BIGINT 外键,引用 test_table.id,表示子实体
a_parent_id BIGINT 外键,引用 test_table.id,表示父实体

约束条件:

  • a_id 和 a_parent_id 都是 test_table 的外键。
  • (a_id, a_parent_id) 组合具有唯一性约束,确保同一对父子关系不会重复。
  • a_parent_id 可以为 NULL,这表示该实体没有父级,可能是关系链的根节点。

基础实体映射

首先,我们定义 test_table 对应的 Hibernate 实体 Test:

PageAdmin企业网站管理系统4.0.25
PageAdmin企业网站管理系统4.0.25

PageAdmin企业网站管理系统V4.0,基于微软最新的MVC框架全新开发,强大的后台管理功能,良好的用户操作体验,可热插拔的插件功能让扩展更加灵活和开放,全部信息表采用自定义表单,可任意自定义扩展字段,支持一对一,一对多的表映射.....各种简单到复杂的网站都可以轻松应付。 PageAdmin V4.0.25更新日志: 1、重写子栏目功能,解决之前版本子栏目数据可能重复的问题 2

下载
import javax.persistence.*;

@Entity
@Table(name = "test_table")
public class Test {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(nullable = false)
    private Long id;

    @Column
    private String comment;

    // 构造函数、Getter和Setter方法省略
    // ...
}

映射自引用多对多关系

为了在 Test 实体中表示其父级和子级关系,我们需要添加两个 @ManyToMany 集合属性。这两个属性都将指向 Test 实体自身,并使用 relation 表作为它们的连接表。

import javax.persistence.*;
import java.util.List;

@Entity
@Table(name = "test_table")
public class Test {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(nullable = false)
    private Long id;

    @Column
    private String comment;

    // 映射父级关系
    @ManyToMany(targetEntity = Test.class)
    @JoinTable(
        name = "relation", // 连接表的名称
        joinColumns = { // 定义当前实体(Test)在连接表中的列
            @JoinColumn(name = "a_id", referencedColumnName = "id") // 当前Test的id对应relation表的a_id
        },
        inverseJoinColumns = { // 定义关联实体(Test,即父级)在连接表中的列
            @JoinColumn(name = "a_parent_id", referencedColumnName = "id") // 关联Test的id对应relation表的a_parent_id
        }
    )
    private List<Test> parents;

    // 映射子级关系
    @ManyToMany(targetEntity = Test.class)
    @JoinTable(
        name = "relation", // 连接表的名称
        joinColumns = { // 定义当前实体(Test)在连接表中的列
            @JoinColumn(name = "a_parent_id", referencedColumnName = "id") // 当前Test的id对应relation表的a_parent_id
        },
        inverseJoinColumns = { // 定义关联实体(Test,即子级)在连接表中的列
            @JoinColumn(name = "a_id", referencedColumnName = "id") // 关联Test的id对应relation表的a_id
        }
    )
    private List<Test> children;

    // 构造函数、Getter和Setter方法省略
    // ...
}

注解详解

  • @ManyToMany(targetEntity = Test.class):

    • 表示这是一个多对多关系。
    • targetEntity = Test.class 明确指出关联的实体类型是 Test 自身,这对于自引用关系至关重要。
  • @JoinTable(name = "relation", ...):

    • 指定了用于维护多对多关系的连接表名称为 relation。
  • joinColumns:

    • 定义了拥有此关联关系的实体(即当前 Test 实例)在连接表 relation 中对应的外键列。
    • 对于 parents 列表: joinColumns = @JoinColumn(name = "a_id", referencedColumnName = "id")
      • 这意味着当查询一个 Test 实例的父级时,该 Test 实例的 id 会去匹配 relation 表中的 a_id 列。
      • referencedColumnName = "id" 指明 a_id 列引用的是 test_table 的 id 列。
    • 对于 children 列表: joinColumns = @JoinColumn(name = "a_parent_id", referencedColumnName = "id")
      • 这意味着当查询一个 Test 实例的子级时,该 Test 实例的 id 会去匹配 relation 表中的 a_parent_id 列。
  • inverseJoinColumns:

    • 定义了关联的实体(即 parents 列表中的父级 Test 实例或 children 列表中的子级 Test 实例)在连接表 relation 中对应的外键列。
    • 对于 parents 列表: inverseJoinColumns = @JoinColumn(name = "a_parent_id", referencedColumnName = "id")
      • 这意味着 relation 表中的 a_parent_id 列指向的是父级 Test 实例的 id。
    • 对于 children 列表: inverseJoinColumns = @JoinColumn(name = "a_id", referencedColumnName = "id")
      • 这意味着 relation 表中的 a_id 列指向的是子级 Test 实例的 id。

核心思想: 理解 joinColumns 和 inverseJoinColumns 的关键在于它们是相对于当前实体而言的。

  • 当获取 parents 时,当前 Test 实例是“子”,其 id 对应 relation.a_id,而“父”的 id 对应 relation.a_parent_id。
  • 当获取 children 时,当前 Test 实例是“父”,其 id 对应 relation.a_parent_id,而“子”的 id 对应 relation.a_id。

关系导航与操作

通过上述映射,你可以像操作普通集合一样来管理 Test 实体之间的父子关系:

// 假设 entityManager 已经初始化
EntityManager entityManager = ...;

// 创建一些Test实体
Test root = new Test();
root.setComment("Root Node");
entityManager.persist(root);

Test parent1 = new Test();
parent1.setComment("Parent 1");
entityManager.persist(parent1);

Test child1 = new Test();
child1.setComment("Child 1");
entityManager.persist(child1);

Test child2 = new Test();
child2.setComment("Child 2");
entityManager.persist(child2);

// 建立关系
// child1 的父级是 parent1
child1.getParents().add(parent1);
parent1.getChildren().add(child1);

// child2 的父级是 parent1
child2.getParents().add(parent1);
parent1.getChildren().add(child2);

// root 是 parent1 的父级
parent1.getParents().add(root);
root.getChildren().add(parent1);

entityManager.flush(); // 将更改同步到数据库

// 查询并导航关系
Test retrievedParent1 = entityManager.find(Test.class, parent1.getId());
System.out.println("Parent 1's children: " + retrievedParent1.getChildren().stream()
                                                    .map(Test::getComment)
                                                    .collect(Collectors.toList()));
// 输出: [Child 1, Child 2]

Test retrievedChild1 = entityManager.find(Test.class, child1.getId());
System.out.println("Child 1's parents: " + retrievedChild1.getParents().stream()
                                                    .map(Test::getComment)
                                                    .collect(Collectors.toList()));
// 输出: [Parent 1]

注意事项:

  • 双向同步: 在建立双向关系时(例如 child1.getParents().add(parent1); 和 parent1.getChildren().add(child1);),务必保持两侧集合的同步,以确保对象模型的一致性。虽然Hibernate在持久化时会处理连接表,但为了避免在内存中出现不一致的状态,手动维护双向关系是推荐的做法。
  • 级联操作(CascadeType): 根据业务需求,可以考虑在 @ManyToMany 注解中添加 cascade 属性,例如 CascadeType.ALL,以便在删除父级时自动删除子级关系,或在持久化父级时自动持久化子级关系。但请谨慎使用,以免意外删除数据。
  • 懒加载(FetchType): 默认情况下,@ManyToMany 关系是懒加载(FetchType.LAZY)的,这意味着只有在访问 getParents() 或 getChildren() 方法时,Hibernate才会去数据库加载相关数据。这有助于提高性能,避免不必要的数据库查询。如果需要立即加载,可以显式设置为 FetchType.EAGER,但这通常不推荐用于多对多关系,因为它可能导致N+1查询问题。
  • a_parent_id 为 NULL 的处理: 数据库中 a_parent_id 可以为 NULL 的设计,在Hibernate中会表现为某个 Test 实例的 parents 列表为空,表示它是根节点。

总结

通过本教程,我们学习了如何在Hibernate中有效地映射自引用多对多关系。关键在于利用 @ManyToMany 注解结合 @JoinTable,并正确配置 joinColumns 和 inverseJoinColumns 来区分父子关系中的当前实体和关联实体在连接表中的角色。这种映射方式为管理复杂的层级结构或网络状数据提供了强大而灵活的解决方案。正确理解和使用这些注解,能够帮助开发者构建出健壮且高效的数据访问层。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
hibernate和mybatis有哪些区别
hibernate和mybatis有哪些区别

hibernate和mybatis的区别:1、实现方式;2、性能;3、对象管理的对比;4、缓存机制。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

158

2024.02.23

Hibernate框架介绍
Hibernate框架介绍

本专题整合了hibernate框架相关内容,阅读专题下面的文章了解更多详细内容。

94

2025.08.06

Java Hibernate框架
Java Hibernate框架

本专题聚焦 Java 主流 ORM 框架 Hibernate 的学习与应用,系统讲解对象关系映射、实体类与表映射、HQL 查询、事务管理、缓存机制与性能优化。通过电商平台、企业管理系统和博客项目等实战案例,帮助学员掌握 Hibernate 在持久层开发中的核心技能。

39

2025.09.02

Hibernate框架搭建
Hibernate框架搭建

本专题整合了Hibernate框架用法,阅读专题下面的文章了解更多详细内容。

72

2025.10.14

c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

254

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

1089

2024.03.01

class在c语言中的意思
class在c语言中的意思

在C语言中,"class" 是一个关键字,用于定义一个类。想了解更多class的相关内容,可以阅读本专题下面的文章。

870

2024.01.03

python中class的含义
python中class的含义

本专题整合了python中class的相关内容,阅读专题下面的文章了解更多详细内容。

30

2025.12.06

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

3

2026.03.11

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 4.3万人学习

C# 教程
C# 教程

共94课时 | 11.1万人学习

Java 教程
Java 教程

共578课时 | 80.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号