Spring Boot中如何向实体的List类型集合添加新条目?
现有如下OauthUser实体类:
@Data @AllArgsConstructor @NoArgsConstructor @Entity class OauthUser( @OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER) var oauthAttributes: List<OauthAttribute>, @NotNull @Column(unique = true) var email: String, var firstName: String, var lastName: String ) : OAuth2User { @Id @NotNull @GeneratedValue(strategy = GenerationType.SEQUENCE) var id: Long? = null }
需求是在用户再次登录时,向oauthAttributes集合添加新条目以保存资料变更历史,但直接调用user.oauthAttributes.add(newEntry)失败(List接口本身没有add方法),强制转换为ArrayList时抛出java.lang.ClassCastException: class org.hibernate.collection.internal.PersistentBag cannot be cast to class java.util.ArrayList异常。
解决方案
方案1:业务代码中转换为可变集合后更新
无需修改实体类,在业务逻辑中把Hibernate返回的不可变集合转为可变列表,添加新元素后重新赋值并保存实体:
// 获取已存在的用户 val existingUser = userRepository.findByEmail(email) // 将原集合转为可变列表并添加新属性 val updatedAttributes = existingUser.oauthAttributes.toMutableList() updatedAttributes.add(newOauthAttribute) // 重新赋值给用户属性 existingUser.oauthAttributes = updatedAttributes // 保存更新后的用户 userRepository.save(existingUser)
方案2:修改实体类集合类型为MutableList
将实体类中的List改为MutableList并初始化,让Hibernate使用支持修改操作的集合实现:
@Data @AllArgsConstructor @NoArgsConstructor @Entity class OauthUser( @OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true) var oauthAttributes: MutableList<OauthAttribute> = mutableListOf(), @NotNull @Column(unique = true) var email: String, var firstName: String, var lastName: String ) : OAuth2User { @Id @NotNull @GeneratedValue(strategy = GenerationType.SEQUENCE) var id: Long? = null }
之后即可直接调用add方法添加元素,再保存用户:
val user = userRepository.findByEmail(email) user.oauthAttributes.add(newOauthAttribute) userRepository.save(user)
注:添加
orphanRemoval = true可确保当从集合中移除元素时,对应的数据库记录会被删除,更贴合变更历史的管理需求。
方案3:在实体类中封装添加方法
在OauthUser类中封装添加属性的逻辑,隐藏集合处理细节,让业务代码更简洁:
@Data @AllArgsConstructor @NoArgsConstructor @Entity class OauthUser( @OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER) var oauthAttributes: List<OauthAttribute>, @NotNull @Column(unique = true) var email: String, var firstName: String, var lastName: String ) : OAuth2User { @Id @NotNull @GeneratedValue(strategy = GenerationType.SEQUENCE) var id: Long? = null // 封装添加属性的方法 fun addOauthAttribute(attribute: OauthAttribute) { this.oauthAttributes = this.oauthAttributes.toMutableList().apply { add(attribute) } } }
业务代码中直接调用方法即可:
val user = userRepository.findByEmail(email) user.addOauthAttribute(newOauthAttribute) userRepository.save(user)
原因说明
Hibernate为了实现持久化集合的状态跟踪和懒加载,会用自定义的集合实现类(如PersistentBag)代理我们定义的List接口。PersistentBag是Hibernate对Bag类型集合的实现,它和ArrayList没有继承关系,因此强制转换会抛出ClassCastException。同时,Hibernate返回的代理集合通常是不可变的,直接调用修改方法会失败。
内容的提问来源于stack exchange,提问作者bosowski

