Spring Kotlin项目JPA复合键单向OneToMany关联item_id不更新求助
单向OneToMany复合键关联保存时item_id始终为0的解决方案
问题背景
在Spring Kotlin项目中配置带复合键的单向OneToMany关联,代码如下:
ItemEntity代码
@Entity @Table(name = "items") data class ItemEntity( // ... 其他字段,如自增ID等 @OneToMany(mappedBy = "id.itemId", cascade = [CascadeType.ALL], orphanRemoval = true) val enchantments: List<ItemEnchantmentEffectEntity> = emptyList(), // ... ) { // Required by Hibernate to instantiate using reflection private constructor() : this( // ... ) }
ItemEnchantmentEffectEntity代码
@Entity @Table(name = "item_enchantment_effects") data class ItemEnchantmentEffectEntity( @EmbeddedId val id: ItemEnchantmentEffectId, @Column(name = "level", nullable = false) val level: Int ) { // Required by JPA to instantiate using reflection private constructor() : this( id = ItemEnchantmentEffectId(0L, ItemEnchantmentType.UNBREAKING), level = 0 ) }
ItemEnchantmentEffectId代码
@Embeddable data class ItemEnchantmentEffectId( @Column(nullable = false) val itemId: Long? = null, @Enumerated(EnumType.STRING) @Column(nullable = false) val type: ItemEnchantmentType ) : Serializable { // Required by JPA to instantiate using reflection private constructor() : this( type = ItemEnchantmentType.UNBREAKING ) }
生成的表结构符合预期(item_enchantment_effects用复合键确保单个物品不能有重复附魔类型),但保存ItemEntity时,数据库中item_id字段始终为0,无法正确填充为物品的自增ID。尝试过反向ManyToOne加@MapsId的方案,但不符合单向关联的需求(需保持实体与业务对象间的单向映射)。
可行解决方案
方案1:手动填充复合键中的itemId
由于单向关联下Hibernate无法自动同步子实体的外键,可分两步操作:先持久化主实体获取ID,再为子实体手动设置itemId后保存:
// 1. 先保存ItemEntity获取生成的ID val savedItem = itemRepository.save(ItemEntity(...)) // 2. 为附魔实体设置正确的itemId val enchantments = listOf( ItemEnchantmentEffectEntity( id = ItemEnchantmentEffectId(savedItem.id, ItemEnchantmentType.UNBREAKING), level = 3 ), // 其他附魔... ) // 3. 更新ItemEntity并保存 val updatedItem = savedItem.copy(enchantments = enchantments) itemRepository.save(updatedItem)
该方案完全保持单向关联的设计,缺点是需要分两次持久化操作,适合对关联控制要求严格的场景。
方案2:修改@OneToMany注解,由主实体维护关联
去掉mappedBy,改用@JoinColumn让主实体维护关联关系,Hibernate会自动填充子实体复合键中的itemId:
@Entity @Table(name = "items") data class ItemEntity( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) // 确保ID自增 val id: Long? = null, // ... 其他字段 @OneToMany(cascade = [CascadeType.ALL], orphanRemoval = true) @JoinColumn( name = "item_id", referencedColumnName = "id", insertable = false, updatable = false ) val enchantments: MutableList<ItemEnchantmentEffectEntity> = mutableListOf(), // ... ) { private constructor() : this(/* ... */) }
注意事项:
- 将
List改为MutableList,因为Hibernate需要修改集合来维护关联状态 - 确保主实体的ID字段配置了自增策略(如
GenerationType.IDENTITY),Hibernate才能正确获取生成的ID并填充到子实体的复合键中 insertable = false, updatable = false避免Hibernate尝试重复插入/更新item_id字段(因为该字段属于复合主键的一部分)
核心原理
单向OneToMany关联中,mappedBy表示由子实体维护关联关系,但由于子实体没有对应的ManyToOne映射,Hibernate无法自动同步主实体的ID到子实体的复合键中。上述两种方案分别通过手动控制或切换关联维护方,解决了ID填充的问题,同时保持了单向关联的设计。
内容的提问来源于stack exchange,提问作者Aethaine
相关产品推荐
相关产品推荐

