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
相关产品推荐
相关产品推荐

