新闻中心
JPA Hibernate中多实体关联的建模:使用中间实体与复合主键

本教程深入探讨了在jpa hibernate中如何优雅地建模多对多关系及涉及多于两个实体的复杂关联。通过引入一个专用的中间实体(join entity)来表示联结表,并结合使用`@embeddedid`定义复合主键以及`@manytoone`和`@mapsid`进行关系映射,我们能够构建出灵活且可扩展的实体模型。这种方法不仅清晰地反映了数据库层面的结构,还为关系本身添加属性或扩展至更多实体提供了可能。
在关系型数据库管理系统(RDBMS)中,传统意义上的“多对多”关系并非直接存在,而是通过一个联结表(Join Table)来实现的。这个联结表通常包含两个外键,分别指向参与多对多关系的两个主表的主键。当这个联结表除了包含外键之外,还需要存储额外的属性(例如,学生对课程的评分、用户在项目中的角色等),或者当关系涉及三个或更多实体时,简单地使用@ManyToMany注解可能无法满足需求。此时,将联结表映射为一个独立的JPA实体,并为其定义复合主键,成为一种强大且灵活的解决方案。
核心策略:引入中间实体
为了在JPA中更精细地控制多对多关系或构建多实体关联,我们通常会引入一个“中间实体”(Join Entity)。这个中间实体直接映射到数据库中的联结表,它拥有自己的生命周期和属性,可以存储关系本身的附加信息。通过这种方式,原本的“多对多”关系被分解为两个“一对多”关系,即主实体与中间实体之间是“一对多”,中间实体与另一个主实体之间也是“一对多”。
定义中间实体与复合主键
当联结表的主键由其所关联的两个(或多个)实体的主键共同构成时,我们需要在JPA中使用复合主键。@EmbeddedId是实现复合主键的一种常用方式,它允许我们将一个自定义的、可嵌入的类作为实体的主键。
首先,定义一个可嵌入的复合主键类:
import j*a.io.Serializable;
import j*a.util.Objects;
import j*ax.persistence.Column;
import j*ax.persistence.Embeddable;
@Embeddable
public class CourseRatingKey implements Serializable {
@Column(name = "student_id")
private Long studentId;
@Column(name = "course_id")
private Long courseId;
public CourseRatingKey() {}
public CourseRatingKey(Long studentId, Long courseId) {
this.studentId = studentId;
this.courseId = courseId;
}
// Getters and Setters
public Long getStudentId() { return studentId; }
public void setStudentId(Long studentId) { this.studentId = studentId; }
public Long getCourseId() { return cour
seId; }
public void setCourseId(Long courseId) { this.courseId = courseId; }
// 必须重写 equals 和 hashCode 方法,这是复合主键的关键
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CourseRatingKey that = (CourseRatingKey) o;
return Objects.equals(studentId, that.studentId) &&
Objects.equals(courseId, that.courseId);
}
@Override
public int hashCode() {
return Objects.hash(studentId, courseId);
}
}接着,创建中间实体并使用@EmbeddedId注解来引用这个复合主键类:
拾贝
一键同步微信读书所有笔记和划线,并在新标签页回顾
186
查看详情
import j*ax.persistence.*;
@Entity
@Table(name = "course_rating") // 映射到数据库中的联结表
public class CourseRating {
@EmbeddedId
private CourseRatingKey id; // 使用复合主键类
// 其他属性,例如评分
@Column(name = "rating")
private int rating;
// ... 构造函数、Getter/Setter 方法
}映射参与实体:@ManyToOne 与 @MapsId
在中间实体中,我们需要将它与参与关系的各个主实体进行关联。由于中间实体中的外键构成了其复合主键的一部分,因此我们需要一种机制来告诉JPA,这些@ManyToOne关联的外键字段实际上是复合主键的组成部分。这就是@MapsId注解的作用。
@MapsId注解用于指定@ManyToOne关联的外键列是@EmbeddedId或@IdClass所定义的复合主键的一部分。它将@ManyToOne关系映射到复合主键的相应属性上。
继续完善CourseRating实体:
import j*ax.persistence.*;
import j*a.util.Objects; // 导入Objects类
@Entity
@Table(name = "course_rating")
public class CourseRating {
@EmbeddedId
private CourseRatingKey id;
@ManyToOne
@MapsId("studentId") // 将此ManyToOne关系的外键映射到CourseRatingKey中的"studentId"部分
@JoinColumn(name = "student_id") // 实际的数据库列名
private Student student;
@ManyToOne
@MapsId("courseId") // 将此ManyToOne关系的外键映射到CourseRatingKey中的"courseId"部分
@JoinColumn(name = "course_id") // 实际的数据库列名
private Course course;
@Column(name = "rating")
private int rating;
public CourseRating() {}
public CourseRating(Student student, Course course, int rating) {
this.student = student;
this.course = course;
this.rating = rating;
// 在构造函数中初始化复合主键,确保其与关联实体的主键一致
this.id = new CourseRatingKey(student.getId(), course.getId());
}
// Getters and Setters
public CourseRatingKey getId() { return id; }
public void setId(CourseRatingKey id) { this.id = id; }
public Student getStudent() { return student; }
public void setStudent(Student student) { this.student = student; }
public Course getCourse() { return course; }
public void setCourse(Course course) { this.course = course; }
public int getRating() { return rating; }
public void setRating(int rating) { this.rating = rating; }
// 同样,equals和hashCode对于实体也很重要
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CourseRating that = (CourseRating) o;
return Objects.equals(id, that.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}建立双向关系:@OneToMany
为了在主实体(如Student和Course)中也能方便地访问其关联的中间实体,我们需要建立反向的@OneToMany关系。
import j*ax.persistence.*;
import j*a.util.HashSet;
import j*a.util.Set;
@Entity
@Table(name = "student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// 与CourseRating建立一对多关系
@OneToMany(mappedBy = "student", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<CourseRating> ratings = new HashSet<>();
public Student() {}
public Student(String name) { this.name = name; }
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Set<CourseRating> getRatings() { return ratings; }
public void setRatings(Set<CourseRating> ratings) { this.ratings = ratings; }
// 辅助方法,用于方便地添加和移除评分
public void addRating(CourseRating rating) {
ratings.add(rating);
rating.setStudent(this);
// 确保复合主以上就是JPA Hibernate中多实体关联的建模:使用中间实体与复合主键的详细内容,更多请关注其它相关文章!
# 多个
# 潼南网站线上推广公司
# 数字营销怎么取消推广
# 沙田家具网站推广哪里好
# 郸城附近网站推广店电话
# 山东莱阳苗木网站推广
# 如何才能让网站优化
# 网站推广截图更改
# 关键词seo排名服务商
# 优步营销推广方案策划
# 乌市seo网络营销技巧
# 这就是
# java
# 见性
# 这是
# 自己的
# 数据库中
# 将此
# 加载
# 拾贝
# 主键
# app
# cad
相关栏目:
【
科技资讯46185 】
【
网络学院92790 】
相关推荐:
Angular响应式表单:实现提交后表单及按钮的禁用与只读化
在命令行怎么运行html项目_命令行运行html项目方法【教程】
谷歌浏览器一键优化方案_谷歌浏览器直达主页极速不卡版
html网页设计源代码怎么运行_运行html网页设计源代码步骤【指南】
拼多多视频播放卡顿如何处理 拼多多视频播放优化技巧
俄罗斯Yandex搜索引擎入口_Yandex官网免登录一键访问
Descript怎样用AI剪辑自动去噪_Descript用AI剪辑自动去噪【自动降噪】
Golang指针如何与map组合使用_Golang map指针组合实践
Golang如何处理RPC请求负载均衡_Golang RPC请求负载均衡策略与实践
必由学登录入口 必由学官方网站在线访问链接
《主播少女的秘密账号迷宫》首支宣传片
Django通过AJAX异步上传图片并保存至模型的完整指南
Yandex浏览器官方网页版入口 Yandex浏览器最新版官网
如何将HTML表格多行数据保存到Google Sheets
PHP表单数据传递:如何通过隐藏输入字段获取动态ID
《GTA6》开发画面疑似泄露!这次可不是AI了
Win10磁盘清理工具在哪 Win10打开并使用磁盘清理【教程】
CSS Box Model与弹性按钮:维持布局稳定的动画实践
响应式图片在网页设计中的正确实现方法
Go语言中Map值调用指针接收器方法的限制与应对
qq游戏大厅官方下载_qq游戏免费下载安装入口
解决Flask中Quill编辑器内容提交失败及TypeError的指南
Lar*el递归关系中排除子孙节点的策略
J*aScript数组对象转换:按指定键分组与值收集
天眼查企业查询官网入口 天眼查官方网页版查询
极速漫画官方主页网址 极速漫画漫画在线浏览官网链接
铁路12306官网网页端快速入口 铁路12306官方首页登录教程
qq邮箱日历功能怎么用_创建日程与会议邀请的技巧
如何使用CaptainHook和Composer管理Git钩子_在提交前自动运行代码检查的Composer配置
抖音未来赚钱的新趋势 2025年值得关注的变现风口分析
Lar*el用户头像管理:实现图片缩放、存储与旧文件安全删除的最佳实践
C++如何实现单例模式_C++设计模式之线程安全的单例写法
微信客户端如何收红包_微信客户端接收红包使用教程
2026春节假期时间安排 2026春节假日查询
邮政快递包裹最新位置 邮政快递实时追踪入口
ExcelARRAYTOTEXT函数怎么自定义分隔符输出数组文本_ARRAYTOTEXT实现动态生成SQL语句
在python-socketio事件处理器中安全访问Flask应用上下文
神经网络二分类模型训练异常:高损失与完美验证准确率的排查与修正
小红书网页版入口链接分享 小红书官网直接进
J*aScript生成器_j*ascript异步迭代
利用5118提升短视频内容效果_5118短视频关键词优化方法
顺丰快递查单号物流信息 顺丰快递小程序查询入口
Yandex搜索引擎一键访问入口_俄罗斯Yandex官网免登录
Go Martini框架:动态服务解码后的图片内容
J*aScript Promise链中如何正确终止后续.then执行并处理错误
Golang如何安装Swagger工具_GoSwagger文档生成环境
在J*a中如何使用Stream.map转换元素_Stream映射操作解析
windows10怎么查看硬盘序列号_windows10硬盘id查询命令
AO3官方可用镜像 Archive of Our Own网页版最新入口
利用Bokeh CustomJS动态控制DataTable列可见性


2025-12-05
浏览次数:次
返回列表
seId; }
public void setCourseId(Long courseId) { this.courseId = courseId; }
// 必须重写 equals 和 hashCode 方法,这是复合主键的关键
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CourseRatingKey that = (CourseRatingKey) o;
return Objects.equals(studentId, that.studentId) &&
Objects.equals(courseId, that.courseId);
}
@Override
public int hashCode() {
return Objects.hash(studentId, courseId);
}
}