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

Spring ManyToOne关联实体Service测试时为null的问题解决

问题:测试时触发NullPointerException,因dormitoryEntity为null

异常信息

java.lang.NullPointerException: Cannot invoke "com.project.clickit.entity.DormitoryEntity.toDTO()" because "this.dormitoryEntity" is null


相关代码

DormitoryEntity实体类

@Setter
@Getter
@Builder
@Table(name = "dormitory")
@NoArgsConstructor
//@AllArgsConstructor
@Entity
public class DormitoryEntity {
    @Id
    @Column(name = "dormitory_id")
    private String id;

    @Column(name = "dormitory_name")
    private String name;

    @Builder
    public DormitoryEntity(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public DormitoryDTO toDTO() {
        return DormitoryDTO.builder()
                .id(this.id)
                .name(this.name)
                .build();
    }
}

FacilityEntity实体类

@Setter
@Getter
@Builder
@Table(name = "facility")
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class FacilityEntity {
    @Id
    @Column(name = "facility_id")
    private String id;

    @Column(name = "facility_name")
    private String name;

    @Column(name = "facility_info")
    private String info;

    @Column(name = "facility_open")
    private Integer open;

    @Column(name = "facility_close")
    private Integer close;

    @Column(name = "facility_img")
    private String img;

    @Column(name = "facility_terms")
    private String terms;

    @Column(name = "facility_extension_limit")
    private Integer extensionLimit;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "facility_dormitory")
    private DormitoryEntity dormitoryEntity;

    public FacilityEntity(String id, String name, String info, Integer open,
                          Integer close, String img, String terms, Integer extensionLimit) {
        this.id = id;
        this.name = name;
        this.info = info;
        this.open = open;
        this.close = close;
        this.img = img;
        this.terms = terms;
        this.extensionLimit = extensionLimit;
    }

    public FacilityDTO toDTO() {
        return FacilityDTO.builder()
                .id(this.id)
                .name(this.name)
                .info(this.info)
                .open(this.open)
                .close(this.close)
                .img(this.img)
                .terms(this.terms)
                .extensionLimit(this.extensionLimit)
                .dormitoryDTO(this.dormitoryEntity.toDTO())
                .build();
    }
}

FacilityRepository

@Query("SELECT new FacilityEntity(f.id, f.name, f.info, f.open, f.close, f.img, f.terms, f.extensionLimit, f.dormitoryEntity) FROM FacilityEntity f where f.id = :id")
FacilityEntity findByFacilityId(@Param("id") String id);

FacilityService

@Transactional
public FacilityDTO findById(String id) {
    return facilityRepository.findByFacilityId(id).toDTO();
}

测试代码

@Slf4j
@ExtendWith({MockitoExtension.class})
public class FacilityServiceTest {
    @Mock
    private FacilityRepository facilityRepository;

    @InjectMocks
    private FacilityService facilityService;

    @Test
    void findByIdTest(){

        given(facilityRepository.findByFacilityId("test_facility")).willReturn(new FacilityEntity());

        FacilityDTO result = facilityService.findById("test_facility");

        assertThat(result).isNotNull;
    }
}

已尝试的解决方法

  • 在@Test注解后添加@Transactional(propagation = Propagation.SUPPORT)注解
  • 将fetchType从LAZY改为EAGER

补充说明

使用Postman发送Get请求时,FacilityService的findById方法运行正常,问题仅出现在使用Mock对象而非真实FacilityRepository的测试场景中。


解决方案

方式一:测试中构造完整的关联实体

测试里返回的new FacilityEntity()未初始化dormitoryEntity字段,导致toDTO()调用时触发空指针。需要构造包含关联宿舍实体的设施对象:

@Test
void findByIdTest(){
    // 构造关联的宿舍实体
    DormitoryEntity dorm = DormitoryEntity.builder()
            .id("test_dorm")
            .name("测试宿舍")
            .build();
    // 构造带关联的设施实体
    FacilityEntity facility = FacilityEntity.builder()
            .id("test_facility")
            .name("测试设施")
            .dormitoryEntity(dorm)
            .build();

    given(facilityRepository.findByFacilityId("test_facility")).willReturn(facility);

    FacilityDTO result = facilityService.findById("test_facility");

    assertThat(result).isNotNull();
    assertThat(result.getDormitoryDTO()).isNotNull();
}

方式二:在toDTO()方法中做空指针防护

修改FacilityEntity的toDTO()方法,处理关联对象为null的情况:

public FacilityDTO toDTO() {
    return FacilityDTO.builder()
            .id(this.id)
            .name(this.name)
            .info(this.info)
            .open(this.open)
            .close(this.close)
            .img(this.img)
            .terms(this.terms)
            .extensionLimit(this.extensionLimit)
            .dormitoryDTO(this.dormitoryEntity != null ? this.dormitoryEntity.toDTO() : null)
            .build();
}

额外优化建议

优先使用Builder模式创建实体对象,而非直接调用构造函数,避免遗漏关联字段或必填参数,减少类似空指针问题的发生。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 14:41:25