Spring Boot Rest Controller保存含子实体的父实体时遇栈溢出
问题原因与解决方案
栈溢出的根源不是account.contacts.forEach触发了Hibernate保存操作,而是Jackson序列化实体时的循环引用:
当控制器返回保存后的Account实体时,Jackson需要把它转成JSON格式。但Account包含Contact集合,每个Contact又持有Account引用,Jackson会不断递归序列化这两个对象,直到栈内存耗尽。
解决方案
方案1:使用DTO(推荐)
创建专门的响应数据传输对象(DTO),只暴露需要返回的字段,避免循环引用,同时精确控制响应格式:
首先定义DTO类:
// 账户响应DTO data class AccountResponse( val id: Long, val name: String, val contacts: List<ContactResponse>, val callServices: Boolean ) // 联系人响应DTO data class ContactResponse( val id: Long, val name: String, val phoneNumber: String, val account: Long // 只返回账户ID,而非整个Account对象 )
修改控制器方法,返回DTO而非实体:
@PostMapping(produces = ["application/json"]) fun createAccount(@RequestBody account: Account): AccountResponse { account.contacts.forEach { c -> c.account = account } val savedAccount = accountRepository.save(account) return AccountResponse( id = savedAccount.id!!, name = savedAccount.name, contacts = savedAccount.contacts.map { contact -> ContactResponse( id = contact.id!!, name = contact.name, phoneNumber = contact.phoneNumber, account = savedAccount.id!! ) }, callServices = savedAccount.callServices ) }
方案2:用Jackson注解处理循环引用
如果不想用DTO,可以通过Jackson注解标记双向关联的序列化规则:
修改实体类:
@Entity class Account { @Id @GeneratedValue var id: Long? = null @Column lateinit var name: String @OneToMany(mappedBy = "account", cascade = [CascadeType.PERSIST]) @JsonManagedReference // 标记正向引用,序列化时正常输出 lateinit var contacts: Set<Contact> @Column var callServices: Boolean = true }
@Entity class Contact { @Id @GeneratedValue var id: Long? = null @Column lateinit var name: String @Column lateinit var phoneNumber: String @ManyToOne @JoinColumn(name = "account_id") @JsonBackReference // 标记反向引用,序列化时忽略该字段 var account: Account? = null }
注意:这种方式下,响应中的Contact对象不会包含account字段(只会有id、name、phoneNumber),如果需要返回account的ID,还是推荐用DTO方案。
补充说明
account.contacts.forEach { c -> c.account = account }这段代码是必要的,它手动建立了联系人与账户的关联关系,确保Hibernate保存联系人时能正确设置account_id外键,本身不会触发保存操作,真正的保存是由accountRepository.save(account)执行的。
内容的提问来源于stack exchange,提问作者Marko Galesic
相关产品推荐
相关产品推荐

