Spring Data JPA findAll()查询子实体仅返回重复首条数据问题
问题:Spring Data JPA查询父实体时子实体重复且仅显示第一条数据
我定义了如下实体类,所有子实体(Address、BankAccounts)的数据已正确保存到数据库中。但使用父实体的findAll()方法查询子实体数据时,仅返回子表的第一条记录且重复显示,数据库中存在正确的多条数据。使用的Spring Data JPA版本为:implementation 'org.springframework.boot:spring-boot-starter-data-jpa:2.0.7.RELEASE'
实体类代码
@Entity @Table(name = "Employee") @AllArgsConstructor @NoArgsConstructor data class EmployeeEntity( ..... @OneToMany(cascade = arrayOf(CascadeType.ALL)) @JoinColumn(name = "id", referencedColumnName = "id") val addresses: List<Address>, @OneToMany(cascade = arrayOf(CascadeType.ALL)) @JoinColumn(name = "id", referencedColumnName = "id") val bankaccounts: List<BankAccounts> )
实际查询结果
[ { ......... "addresses": [ { "address": "address 1" }, { "address": "address 1" } ], "bankaccounts": [ { "account": "account 1" }, { "account": "account 1" } ] } ]
预期查询结果
[ { ......... "addresses": [ { "address": "address 1" }, { "address": "address 2" } ], "bankaccounts": [ { "account": "account 1" }, { "account": "account 2" } ] } ]
仓库接口
@Repository interface EmployeeRepository : JpaRepository<EmployeeEntity, String?> { }
问题原因及解决办法
核心原因
- 关联映射配置错误:两个
@OneToMany注解的@JoinColumn都用了name = "id", referencedColumnName = "id",意味着子表用自身的主键id关联父表的id,完全不符合外键关联逻辑,导致JPA无法正确识别子实体归属。 - 笛卡尔积导致重复:同时查询多个集合关联时,JPA执行笛卡尔积查询,再加上映射错误,最终返回重复的第一条子实体数据。
- Kotlin数据类特性问题:
data class自动生成的equals/hashCode包含集合字段,JPA处理懒加载或查询结果时,可能误判对象重复,重复添加相同实例。
解决步骤
1. 修正关联映射配置
给子表添加指向父实体的外键字段(如employee_id),并修改@JoinColumn配置:
@Entity @Table(name = "Employee") @AllArgsConstructor @NoArgsConstructor data class EmployeeEntity( @Id val id: String, // 父实体主键 @OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY) @JoinColumn(name = "employee_id", referencedColumnName = "id") // 子表外键列 -> 父表主键 val addresses: List<Address> = emptyList(), @OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY) @JoinColumn(name = "employee_id", referencedColumnName = "id") val bankaccounts: List<BankAccounts> = emptyList() )
子实体示例(Address):
@Entity @Table(name = "Address") data class Address( @Id val id: String, val address: String, @Column(name = "employee_id") val employeeId: String // 关联父实体的id )
2. 避免笛卡尔积问题
使用@Fetch(FetchMode.SUBSELECT)让JPA通过子查询分别加载每个集合,避免笛卡尔积:
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY) @JoinColumn(name = "employee_id", referencedColumnName = "id") @Fetch(FetchMode.SUBSELECT) val addresses: List<Address> = emptyList(), @OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY) @JoinColumn(name = "employee_id", referencedColumnName = "id") @Fetch(FetchMode.SUBSELECT) val bankaccounts: List<BankAccounts> = emptyList()
3. 修正Kotlin数据类问题(可选但推荐)
手动重写equals/hashCode,仅基于主键字段判断,避免集合字段影响:
@Entity @Table(name = "Employee") @AllArgsConstructor @NoArgsConstructor class EmployeeEntity( @Id val id: String, @OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY) @JoinColumn(name = "employee_id", referencedColumnName = "id") val addresses: List<Address> = emptyList(), @OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY) @JoinColumn(name = "employee_id", referencedColumnName = "id") val bankaccounts: List<BankAccounts> = emptyList() ) { override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as EmployeeEntity return id == other.id } override fun hashCode(): Int { return id.hashCode() } }
4. 验证数据库数据
检查子表(Address、BankAccounts)的employee_id字段是否正确关联到对应父实体的id,确保数据关联关系无误。
内容的提问来源于stack exchange,提问作者Hiran
相关产品推荐
相关产品推荐

