解决JPA中嵌入式主键与多对一关系导致的Null ID生成错误

解决jpa中嵌入式主键与多对一关系导致的null id生成错误

本文探讨了在使用JPA和Hibernate时,当复合主键包含外键且使用`@EmbeddedId`注解时,可能遇到的Null ID生成错误。通过将多对一(`@ManyToOne`)关系直接嵌入到`@Embeddable`类中,并手动构建复合ID实例,可以有效解决此问题,确保实体正确持久化。

在JPA和Hibernate的实体关系映射中,复合主键(Composite Primary Key)是一个常见的需求。当复合主键的一部分同时作为外键引用另一个实体时,使用@Embeddable和@EmbeddedId注解来定义这种复合主键是推荐的做法。然而,在这种特定场景下,如果不正确配置,可能会遇到“Null ID generated”的错误,尤其是在尝试持久化新实体时。

理解问题:嵌入式主键与多对一关系的冲突点

考虑一个场景,我们有一个Block实体和一个BlockAttribute实体。BlockAttribute的标识符由Block的ID和一个attribute字符串组成,形成一个复合主键。BlockAttribute同时通过一个ManyToOne关系引用Block实体。

最初,数据模型可能如下所示:

1. 嵌入式主键类 BlockAttributeID:

@Embeddable@Datapublic class BlockAttributeID implements Serializable {    @Column(name = "block_id")    Long blockID; // 引用Block的ID    String attribute;    // equals和hashCode方法省略,但必须正确实现    @Override    public boolean equals(Object o) { /* ... */ }    @Override    public int hashCode() { /* ... */ }}

2. 实体类 BlockAttribute:

@Data@Table(name = "block_attribute")@Entitypublic class BlockAttribute {    @EmbeddedId    BlockAttributeID blockAttributeID; // 嵌入式复合主键    @ManyToOne(fetch = FetchType.LAZY)    @JsonIgnore    @JoinColumn(name = "block_id") // 再次声明block_id外键    Block block; // 多对一关系    String label;    // ... 其他属性    // equals和hashCode方法省略,但必须正确实现    @Override    public boolean equals(Object o) { /* ... */ }    @Override    public int hashCode() { /* ... */ }}

在这种设计中,BlockAttributeID包含blockID,而BlockAttribute实体本身又通过@ManyToOne注解维护了与Block的关联。当尝试保存一个新的BlockAttribute实例时,如果BlockAttributeID中的blockID没有被显式设置,或者Hibernate无法自动从BlockAttribute的block字段中推断并填充到EmbeddedId中,就会导致Null ID generated for: class BlockAttribute的错误。

问题在于,虽然BlockAttribute有一个@ManyToOne Block block字段,但BlockAttributeID中的blockID字段是独立的。Hibernate在处理@EmbeddedId时,期望其所有组件都能在持久化之前被完全填充。如果blockID作为复合主键的一部分是空的,它就无法生成有效的标识符。

Revid AI Revid AI

AI短视频生成平台

Revid AI 96 查看详情 Revid AI

解决方案:将多对一关系内嵌到 @Embeddable 中

解决此问题的核心思想是:如果复合主键的某个组件本身就是一个外键,那么这个外键关系应该直接在@Embeddable类中定义,而不是在主实体类中重复定义。这样,@EmbeddedId就能直接包含对关联实体的引用,从而确保复合主键的完整性。

1. 修改 BlockAttributeID 类:将对Block实体的引用直接移动到BlockAttributeID中,并移除blockID字段。

@Embeddable@Data // 或手动添加getter/setterpublic class BlockAttributeID implements Serializable {    // 直接在嵌入式ID中定义ManyToOne关系    @ManyToOne(fetch = FetchType.LAZY)    @JsonIgnore // 如果需要,忽略JSON序列化    @JoinColumn(name = "block_id") // 指定外键列    Block block; // 引用Block实体本身    String attribute;    // 构造函数用于方便创建ID实例    public BlockAttributeID(Block block, String attribute) {        this.block = block;        this.attribute = attribute;    }    public BlockAttributeID() {        // 默认构造函数,JPA要求    }    @Override    public boolean equals(Object o) {        if (this == o) return true;        if (!(o instanceof BlockAttributeID)) return false;        // 比较时使用Block实体和attribute        BlockAttributeID that = (BlockAttributeID) o;        return block != null && attribute != null && block.equals(that.getBlock()) && attribute.equals(that.getAttribute());    }    @Override    public int hashCode() {        // 推荐使用Objects.hash(block, attribute) 或其他更健壮的哈希算法        // 简单的实现可以基于block的ID和attribute的哈希值        return getClass().hashCode(); // 也可以根据实际情况调整,例如 Objects.hash(block.getBlockID(), attribute);    }}

关键改动:

Long blockID 被移除。@ManyToOne Block block 被添加到BlockAttributeID中。这意味着BlockAttributeID现在直接持有Block实体的引用,而不是仅仅它的ID。@JoinColumn(name = “block_id”) 仍然存在,它告诉Hibernate如何映射这个外键列。

2. 修改 BlockAttribute 类:由于BlockAttributeID现在已经包含了Block的引用,BlockAttribute实体中原有的@ManyToOne Block block字段就变得多余了,可以将其移除。

@Data@Table(name = "block_attribute")@Entitypublic class BlockAttribute {    @EmbeddedId    BlockAttributeID blockAttributeID; // 嵌入式复合主键,现在它包含了Block的引用    // 原有的 @ManyToOne Block block 字段被移除    String label;    @Enumerated(EnumType.STRING)    Type type;    // ... 其他属性    @Override    public boolean equals(Object o) {        if (this == o) return true;        if (!(o instanceof BlockAttribute)) return false;        BlockAttribute that = (BlockAttribute) o;        return blockAttributeID != null && blockAttributeID.equals(that.getBlockAttributeID());    }    @Override    public int hashCode() {        return getClass().hashCode();    }}

3. 更新保存逻辑:在创建和保存BlockAttribute时,需要手动构建BlockAttributeID实例,并确保其中的Block实体是已持久化的(即具有有效的ID)。

// 假设 block 是一个已经持久化并具有有效ID的Block实体// block = blockRepository.save(block); // 确保block已保存并获取其ID// 创建BlockAttribute实例BlockAttribute blockAttribute = new BlockAttribute();// ... 设置blockAttribute的其他属性// 手动创建并设置BlockAttributeID// 确保这里的 'block' 是已经从数据库中加载或刚刚保存的,具有有效ID的Block实例BlockAttributeID blockAttributeID = new BlockAttributeID(block, completeBlockDTO.getBlockAttributeDTO().getAttribute());blockAttribute.setBlockAttributeID(blockAttributeID);// 保存BlockAttributeblockAttributeRepository.save(blockAttribute);

关键注意事项与最佳实践

持久化顺序: 在保存具有复合主键的子实体(如BlockAttribute)之前,必须确保其引用的父实体(如Block)已经持久化,并且拥有一个有效的ID。否则,BlockAttributeID中的block引用将指向一个瞬态(transient)实体,导致错误。

// 1. 保存父实体以获取其IDBlock savedBlock = blockRepository.save(block);// 2. 使用已保存的父实体创建子实体的嵌入式IDBlockAttribute blockAttribute = new BlockAttribute();blockAttribute.setBlockAttributeID(    new BlockAttributeID(savedBlock, completeBlockDTO.getBlockAttributeDTO().getAttribute()));// ... 设置blockAttribute的其他属性// 3. 保存子实体blockAttributeRepository.save(blockAttribute);

equals() 和 hashCode() 实现:在@Embeddable类中,equals()和hashCode()方法的正确实现至关重要。它们应该基于构成复合主键的所有字段来生成。在我们的例子中,是block和attribute。直接使用getClass().hashCode()可能会导致集合操作(如HashSet)中的意外行为。更健壮的实现应考虑Block实体的ID以及attribute字符串。例如:

@Overridepublic boolean equals(Object o) {    if (this == o) return true;    if (o == null || getClass() != o.getClass()) return false;    BlockAttributeID that = (BlockAttributeID) o;    return Objects.equals(block, that.block) && Objects.equals(attribute, that.attribute);}@Overridepublic int hashCode() {    return Objects.hash(block, attribute);}

这里需要注意的是,Objects.equals(block, that.block) 会比较两个Block对象是否相等,而Block的equals方法通常会基于其主键blockID。

避免冗余映射: 将ManyToOne关系直接嵌入到@Embeddable中后,主实体(BlockAttribute)就不再需要重复声明这个ManyToOne关系了。这简化了映射,减少了潜在的混淆。

总结

当在JPA中使用@EmbeddedId来定义包含外键的复合主键时,最佳实践是将该外键的@ManyToOne关系直接定义在@Embeddable类中。这种方法确保了复合主键的完整性,并避免了由于主键组件未正确填充而导致的“Null ID generated”错误。同时,务必注意实体持久化的顺序以及equals()和hashCode()方法的正确实现,以保证数据一致性和集合操作的正确性。

以上就是解决JPA中嵌入式主键与多对一关系导致的Null ID生成错误的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1074850.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
OPPO Reno12系列核心配置来了:首发天玑双芯 支持红外
上一篇 2025年12月2日 07:06:37
google浏览器官网网页版进入 google浏览器官网网页版搜索地址
下一篇 2025年12月2日 07:06:43

相关推荐

  • Java多线程API调用中Future.get()返回null的解决方案

    本文旨在解决%ignore_a_1%api调用中`future.get()`方法返回`null`的常见问题。当使用`callable`和`executorservice`并发执行api请求并尝试获取结果时,如果流读取逻辑不当,可能导致获取到的数据为空。文章将详细解释问题根源,并提供使用`string…

    2026年9月21日
    000
  • 哪些Docker扩展能让你在VSCode内轻松管理容器?

    Docker官方扩展是VSCode中管理容器的核心工具,提供容器、镜像、卷、网络的可视化操作,结合Remote-Containers可实现容器内开发,辅以YAML、GitLens等扩展提升效率,需确保本地Docker daemon运行。 在 VSCode 中管理 Docker 容器,最核心的扩展是 …

    2026年9月21日
    000
  • VSCode远程开发:配置容器与SSH连接的最佳实践解析

    使用VSCode远程开发提升效率,通过Remote-Containers和Remote-SSH实现环境标准化。1. 配置.devcontainer文件夹,用devcontainer.json定义容器环境,推荐自定义Dockerfile并预装工具;2. SSH连接需配置公钥认证、~/.ssh/conf…

    2026年9月21日
    100
  • Linux查看系统日志的常用命令

    答案是查看Linux日志需综合使用journalctl、dmesg、tail、grep等工具。journalctl用于systemd系统集中查询服务及内核日志,支持时间、优先级、字段等多维度过滤;dmesg专注内核启动与硬件问题;tail -f实时监控日志动态;cat、grep、less结合正则和管…

    用户投稿 2026年9月21日
    000
  • mysql如何实现后台管理系统

    答案:基于MySQL的%ignore_a_1%需设计用户、权限、日志等表结构,通过后端语言实现安全的CRUD接口与JWT认证,前端展示数据并控制权限,确保系统安全稳定。 实现一个基于 MySQL 的后台管理系统,核心是构建一个安全、稳定、可扩展的系统架构,将数据库作为数据存储层,配合后端语言和前端界…

    2026年9月21日
    000
  • 如何为VSCode设置自定义的代码高亮颜色?

    答案:通过settings.json中的editor.tokenColorCustomizations可自定义VSCode代码高亮颜色,支持全局或特定主题下修改关键字、字符串等元素颜色,结合textMateRules和作用域精确控制,提升代码可读性。 为 VSCode 设置自定义的代码高亮颜色,可以…

    2026年9月21日
    000
  • 如何配置VSCode来完美支持Vue.js开发?

    安装Volar、TypeScript Vue Plugin、ESLint和Prettier扩展,禁用Vetur,在settings.json中配置vetur.enabled为false,设置ESLint保存时自动修复并指定Prettier为默认格式化工具,关联.vue文件语言,启用TypeScrip…

    2026年9月21日
    000
  • JavaScript中的模块联邦如何实现微前端的代码共享?

    模块联邦通过运行时动态加载实现微前端代码共享,无需打包公共依赖。使用 ModuleFederationPlugin 配置 name、remotes、exposes 和 shared,使应用可暴露或引入远程模块,支持组件、工具函数及状态管理共享,提升复用性并减少冗余。 模块联邦通过在构建时让不同应用直…

    2026年9月21日
    200
  • VSCode的代码折叠功能好用吗?

    VSCode代码折叠功能支持多种方式:点击箭头、快捷键、命令面板及按区域类型折叠;可自定义基于缩进的折叠、默认层级和提示装饰器;集成语言服务后能智能识别JSX、Vue组件等结构,提升大型文件编辑效率。 VSCode 的代码折叠功能非常实用,尤其在处理大型文件或复杂结构时能显著提升阅读和编辑效率。 支…

    2026年9月21日
    200
  • 如何备份VSCode的全部设置和扩展?

    备份VSCode全部设置和扩展需保存配置文件与扩展目录;2. 配置文件位于各系统指定路径的User文件夹内,包含settings.json和keybindings.json;3. 通过code –list-extensions导出扩展列表并用xargs批量重装可恢复扩展;4. 推荐直接复…

    2026年9月21日
    100
  • Laravel 8 登录后重定向到仪表盘的全面指南

    本文深入探讨了 Laravel 8 中用户登录后重定向到仪表盘的多种策略。我们将详细解析默认的重定向机制,包括 LoginController 和 RedirectIfAuthenticated 中间件,并重点介绍如何通过自定义登录逻辑实现精确的重定向控制,同时提供示例代码和常见问题排查建议,确保用…

    2026年9月21日
    200
  • 怎样配置VSCode与Jest、Cypress等测试框架进行集成测试?

    首先安装Jest和Cypress插件及依赖,配置jest.config.js和.vscode/settings.json实现Jest自动运行,再通过launch.json添加Cypress调试配置,最后在package.json中定义统一脚本命令,使两者在VSCode中高效协同工作。 要在 VSCo…

    2026年9月21日
    000
  • VSCode的括号匹配功能如何自定义?

    可通过 settings.json 自定义括号高亮的边框和背景色;2. 用 editor.matchBrackets 控制是否启用高亮;3. 启用 bracketPairColorization 可为嵌套括号着色;4. 使用 Ctrl/Cmd + Shift + 快速跳转配对括号。 VSCode 的…

    2026年9月21日
    100
  • JSF应用中Markdown文档动态链接处理指南

    本教程旨在解决jsf web应用程序中集成markdown文档时,如何动态处理内部链接以实现页面局部更新的问题。通过结合服务器端markdown渲染和客户端javascript事件监听,我们可以拦截markdown生成的html链接点击事件,利用ajax异步加载并渲染目标markdown文件,从而在…

    2026年9月21日
    600
  • mysql如何理解数据压缩

    MySQL数据压缩通过减少存储空间提升I/O效率,主要在InnoDB引擎中实现页级压缩,使用zlib算法对BLOB、TEXT等大字段表压缩效果显著,需设置ROW_FORMAT=COMPRESSED和KEY_BLOCK_SIZE;压缩可降低磁盘使用并加速全表扫描,但增加CPU开销,频繁更新可能导致页分…

    2026年9月21日
    000
  • edge浏览器如何安装Chrome网上应用店的扩展_edge浏览器安装Chrome扩展方法

    答案:可通过四种方法在Edge浏览器中安装Chrome扩展。1、直接从Chrome网上应用店添加扩展,Edge会自动识别并安装;2、手动下载CRX或ZIP文件,通过开启开发者模式加载已解压的扩展程序;3、从Crx4Chrome等第三方平台下载兼容扩展并按手动方式安装;4、对扩展的manifest.j…

    2026年9月21日
    000
  • VSCode的快捷键映射冲突怎么办?

    首先查看快捷键绑定情况,通过Ctrl+K再Ctrl+S打开键盘快捷方式界面,搜索目标键组合如Ctrl+Shift+P,查看并识别冲突命令;接着右键冲突命令选择“更改键绑定”设置新组合或删除多余绑定;为精确定位,可查看命令ID如workbench.action.quickOpen区分相似命令;若调整较…

    2026年9月21日
    000
  • 在Java中如何创建一个天气查询小应用

    注册OpenWeatherMap获取API密钥;2. 使用Java 11+的HttpClient发送HTTP请求;3. 构造带城市参数的URL并调用天气接口;4. 解析返回的JSON数据提取温度和天气描述;5. 在控制台输出结果,支持中文城市需URL编码。 在Java中创建一个天气查询小应用,核心是…

    2026年9月21日
    000
  • 如何自定义代码的格式化规则?

    自定义代码格式化规则需选择合适工具并配置文件实现统一风格。1. 根据语言选用主流工具如Prettier、Black、clang-format等;2. 在项目根目录创建对应配置文件如.prettierrc、.eslintrc.js或pyproject.toml,定义缩进、引号、行宽等规则;3. 将配置…

    2026年9月21日
    100
  • 怎样在VSCode中快速生成注释文档?

    安装插件如Document This和Koro File Header,通过快捷键在VSCode中快速生成函数及文件注释,支持自定义模板,提升注释效率与规范性。 在 VSCode 中快速生成注释文档,主要依赖插件和快捷键配合代码语言特性来实现。不同编程语言支持方式略有差异,但核心思路是使用智能提示和…

    2026年9月21日
    200

发表回复

登录后才能评论
关注微信