You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Hibernate持久化一对一共享主键触发IdentifierGenerationException问题排查

问题:Hibernate一对一主键共享持久化失败(IdentifierGenerationException)

环境与需求

  • MySQL数据库
  • Hibernate 6.6.0.Final(无Spring)
  • film与film_text为一对一关联,film.film_id为自增主键,需作为film_text.film_id的共享主键
  • Film实体关联Text实体,Text实体不对外反向关联Film

表结构

film表

CREATE TABLE `film` (
  `film_id` smallint unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(128) NOT NULL,
  `description` text,
  `release_year` year DEFAULT NULL,
  `language_id` tinyint unsigned NOT NULL,
  `original_language_id` tinyint unsigned DEFAULT NULL,
  `rental_duration` tinyint unsigned NOT NULL DEFAULT '3',
  `rental_rate` decimal(4,2) NOT NULL DEFAULT '4.99',
  `length` smallint unsigned DEFAULT NULL,
  `replacement_cost` decimal(5,2) NOT NULL DEFAULT '19.99',
  `rating` enum('G','PG','PG-13','R','NC-17') DEFAULT 'G',
  `special_features` set('Trailers','Commentaries','Deleted Scenes','Behind the Scenes') DEFAULT NULL,
  `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`film_id`),
  KEY `idx_fk_language_id` (`language_id`),
  KEY `idx_fk_original_language_id` (`original_language_id`),
  CONSTRAINT `fk_film_language` FOREIGN KEY (`language_id`) REFERENCES `language` (`language_id`) ON DELETE RESTRICT ON UPDATE CASCADE,
  CONSTRAINT `fk_film_language_original` FOREIGN KEY (`original_language_id`) REFERENCES `language` (`language_id`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB;

film_text表

CREATE TABLE `film_text` (
  `film_id` smallint NOT NULL,
  `title` varchar(255) NOT NULL,
  `description` text,
  PRIMARY KEY (`film_id`),
  FULLTEXT KEY `idx_title_description` (`title`, `description`)
) ENGINE=InnoDB;

当前实体映射代码

Film实体

@Entity
@Table(schema = "movie", name = "film")
@Getter
@Setter
@NoArgsConstructor
@RequiredArgsConstructor
public class Film {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "film_id")
    private Integer id;

    @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
    @MapsId
    @JoinColumn(name = "film_id", nullable = false)
    @NonNull
    private Text text;

    // other fields and relationships
}

Text实体

@Entity
@Table(schema = "movie", name = "film_text")
@Getter
@Setter
@NoArgsConstructor
@RequiredArgsConstructor
public class Text {
    @Id
    @Column(name = "film_id")
    @Setter(AccessLevel.NONE)
    private Integer id;

    @Column(nullable = false)
    @NonNull
    private String title;

    @Column(length = 65_535)
    private String description;
}

报错信息

Identifier of entity 'Text' must be manually assigned before calling 'persist()'
org.hibernate.id.IdentifierGenerationException: Identifier of entity 'Text' must be manually assigned before calling 'persist()'

问题原因

你把@MapsId的使用逻辑完全搞反了:@MapsId的作用是让当前实体的主键复用关联实体的主键。你在Film实体的@OneToOne上添加该注解,意味着Film的ID要从Text的ID获取,这与你的需求(Text复用Film的自增ID)完全冲突,导致Hibernate无法自动传递ID,触发持久化异常。

修正方案

方案一:标准映射(推荐)

通过调整@MapsId的位置,让Text实体复用Film的主键,同时隐藏Text中的Film关联字段,满足不反向关联的需求。

修正后的Film实体

移除@MapsId,保持自增ID和级联配置:

@Entity
@Table(schema = "movie", name = "film")
@Getter
@Setter
@NoArgsConstructor
@RequiredArgsConstructor
public class Film {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "film_id")
    private Integer id;

    @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "film_id", referencedColumnName = "film_id", nullable = false)
    @NonNull
    private Text text;

    // 其他字段和关联关系
}

修正后的Text实体

添加@OneToOne关联Film并使用@MapsId,同时隐藏关联字段:

@Entity
@Table(schema = "movie", name = "film_text")
@Getter
@Setter
@NoArgsConstructor
public class Text {
    @Id
    @Column(name = "film_id")
    private Integer id;

    // 仅用于Hibernate映射,不对外暴露getter/setter
    @OneToOne
    @MapsId
    @JoinColumn(name = "film_id")
    private Film film;

    @Column(nullable = false)
    private String title;

    @Column(length = 65_535)
    private String description;

    // 自定义构造方法,避免暴露film参数
    public Text(String title) {
        this.title = title;
    }

    public Text(String title, String description) {
        this.title = title;
        this.description = description;
    }
}

方案二:手动赋值ID(简化版)

如果不想在Text中添加关联字段,可以为Text的ID指定IDENTITY生成策略,在持久化时手动将Film的ID赋值给Text:

// Film实体移除@MapsId,保持原有其他配置
// Text实体修改ID字段:
@Id
@Column(name = "film_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;

// 持久化代码示例
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

Text text = new Text("Test Title", "Test Description");
Film film = new Film(text);
session.persist(film);
// 手动赋值生成的ID
text.setId(film.getId());
tx.commit();
session.close();

验证效果

修正后,持久化Film时,Hibernate会先插入film表获取自增ID,自动将该ID赋值给Text的主键,再插入film_text表,实现主键共享。

内容的提问来源于stack exchange,提问作者Dmytro Kostenko

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 04:47:33