You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JPA OneToOne关联序列化栈溢出及DTO映射失效问题求助

问题解决步骤

1. 修复实体类基础错误

你的Person.java类名误写为Personal,但Address实体和PersonService中引用的都是Person,这会直接导致JPA关联映射失败,是关联字段为null的核心原因之一。同时Address的主键配置也存在冲突,一并修正:

修正Person实体

@Entity
@Getter
@Setter
@RequiredArgsConstructor
public class Person implements Serializable { // 类名从Personal改为Person
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String personName;
    private String personLastName;

    // 主控方,拥有外键,无需mappedBy
    @OneToOne(cascade = CascadeType.ALL)
    private Address address;
}

修正Address实体

@MapsId表示用关联Person的ID作为自身主键,因此不需要再给Address的ID添加@GeneratedValue:

@Entity
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class Address implements Serializable {

    @Id
    private Integer id; // 去掉@GeneratedValue,ID由关联的Person决定

    @Column(name = "Street")
    private String street;
    @Column(name = "Alley")
    private String alley;
    @Column(name = "District")
    private String district;

    // 被控方,mappedBy指定主控方的属性名,@MapsId绑定主键
    @OneToOne(fetch = FetchType.EAGER, mappedBy = "address")
    @MapsId
    private Person person;

    public Address(String street, String alley, String district, Person person) {
        this.street = street;
        this.alley = alley;
        this.district = district;
        this.person = person;
    }
}

2. 解决序列化栈溢出问题

方案一:Jackson注解控制序列化(无需DTO)

使用@JsonIdentityInfo,它会在第一次序列化对象时输出完整内容,后续循环引用时用对象ID代替,既避免栈溢出,又保留关联数据:
在Person和Address类上都添加该注解:

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")

示例(Person类):

@Entity
@Getter
@Setter
@RequiredArgsConstructor
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Person implements Serializable {
    // ... 其他代码
}

方案二:DTO+MapStruct正确映射(推荐)

之前DTO映射后personDTO为null,是因为MapStruct默认不会自动处理关联对象映射,需显式配置:

修正MapStruct映射器

让AddressMapper依赖PersonMapper,并指定关联字段映射规则:

@Mapper(uses = PersonMapper.class) // 引入PersonMapper处理关联对象
public interface AddressMapper {
    AddressMapper INSTANCE = Mappers.getMapper(AddressMapper.class);

    Address addressDtoToAddress(AddressDTO addressDTO);

    @Mapping(source = "person", target = "personDTO") // 显式指定Person到personDTO的映射
    AddressDTO addressToAddressDto(Address address);
}

确保JPA查询加载关联对象

修改AddressRepository,用JOIN FETCH强制加载关联的Person,避免懒加载导致null:

public interface AddressRepository extends JpaRepository<Address, Integer> {
    @Query("SELECT a FROM Address a JOIN FETCH a.person WHERE a.id = :id")
    Optional<Address> findByIdWithPerson(Integer id);
}

同步修改AddressServices的查询方法:

public Optional<Address> getAddress(Integer id){
    return addressRepository.findByIdWithPerson(id);
}

Controller返回DTO

在接口中转换为DTO后返回,彻底避免实体类序列化循环:

@GetMapping("/get-address")
public ResponseEntity<AddressDTO> getAddress(@RequestParam Integer id) {
    return addressServices.getAddress(id)
            .map(address -> ResponseEntity.ok(AddressMapper.INSTANCE.addressToAddressDto(address)))
            .orElse(ResponseEntity.notFound().build());
}

3. 修正数据保存逻辑

PersonService中属性名错误(实体类是personName,代码中用了userName),修正后确保关联关系正确:

public String createUser(){
    Person u1 = new Person();
    u1.setPersonName("test");
    u1.setPersonLastName("test");
    Address address = new Address("test", "test", "test", u1);
    u1.setAddress(address);
    personRepository.save(u1);
    return "Save is successful";
}

内容的提问来源于stack exchange,提问作者smhylc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 16:55:25