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

Spring项目POST MaterialLists遇非空属性空值引用错误求解决

问题:创建MaterialLists实体时触发DataIntegrityViolationException错误

使用Java + Spring开发零件管理应用,包含Parts和MaterialLists两个实体。创建Parts实体功能正常,但创建MaterialLists时抛出如下错误:

app-1  | 2024-07-15T17:18:03.404Z DEBUG 1 --- [io-8081-exec-10] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.dao.DataIntegrityViolationException: not-null property references a null or transient value : com.lbx.parts.entity.MaterialList.materialList]

相关代码与请求信息

Parts实体类

package com.lbx.parts.entity;

import jakarta.persistence.*;
import lombok.*;
import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity(name = "Parts")
@Table
public class Parts {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true)
    private String partNumber;
    private String description;
    private String manufacturer;
    private String supplier;
    private String serialNumber;

    @Enumerated(EnumType.STRING)
    private PartStatus status;

    private String category;
    private int quantityOnHand;
    private String storageLocation;

    @Temporal(TemporalType.DATE)
    private Date expirationDate;

    private int minQuantity;
    private int maxQuantity;
    private double cost;
    private int leadTime;
    private String processCriticality;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "material_list_id", nullable = false)
    private MaterialLists materialList;

    public enum PartStatus {
        ACTIVE,
        INACTIVE
    }
}

MaterialLists实体类与服务实现

package com.lbx.parts.entity;

import jakarta.persistence.*;
import lombok.*;
import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class MaterialLists {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long materialListID;

    private String description;

    @OneToMany(mappedBy = "materialList", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Parts> parts;

    public void addPart(Parts part) {
        parts.add(part);
        part.setMaterialList(this);
    }
}

// 服务实现类
package com.lbx.parts.service.impl;

import com.lbx.parts.dto.MaterialListsDto;
import com.lbx.parts.entity.MaterialLists;
import com.lbx.parts.entity.Parts;
import com.lbx.parts.mapper.MaterialListsMapper;
import com.lbx.parts.mapper.PartsMapper;
import com.lbx.parts.repository.MaterialListsRepository;
import com.lbx.parts.service.MaterialListsService;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

@Service
@AllArgsConstructor
public class MaterialListsServiceImpl implements MaterialListsService {

    private final MaterialListsRepository materialListRepository;

    @Override
    public MaterialListsDto createMaterialList(MaterialListsDto materialListsDto) {
        MaterialLists materialLists = MaterialListsMapper.mapToMaterialLists(materialListsDto);

        // Set the material list reference in each part
        if (materialLists.getParts() != null) {
            for (Parts part : materialLists.getParts()) {
                part.setMaterialList(materialLists);
            }
        }

        MaterialLists savedMaterialLists = materialListRepository.save(materialLists);
        return MaterialListsMapper.mapToMaterialListsDto(savedMaterialLists);
    }

    @Override
    public MaterialListsDto getMaterialListById(Long materialListId) {
        MaterialLists materialLists = materialListRepository.findById(materialListId)
                .orElseThrow(() -> new IllegalArgumentException("Invalid material list Id:" + materialListId));
        return MaterialListsMapper.mapToMaterialListsDto(materialLists);
    }

    @Override
    public List<MaterialListsDto> getAllMaterialLists() {
        List<MaterialLists> materialLists = materialListRepository.findAll();
        return materialLists.stream()
                .map(MaterialListsMapper::mapToMaterialListsDto)
                .collect(Collectors.toList());
    }

    @Override
    public MaterialListsDto updateMaterialList(Long materialListId, MaterialListsDto updatedMaterialListDto) {
        MaterialLists materialLists = materialListRepository.findById(materialListId)
                .orElseThrow(() -> new IllegalArgumentException("Invalid material list Id:" + materialListId));

        materialLists.setDescription(updatedMaterialListDto.getDescription());
        materialLists.setParts(updatedMaterialListDto.getParts().stream()
                .map(PartsMapper::mapToParts)
                .collect(Collectors.toList()));

        // Set the material list reference in each part
        if (materialLists.getParts() != null) {
            for (Parts part : materialLists.getParts()) {
                part.setMaterialList(materialLists);
            }
        }

        MaterialLists updatedMaterialLists = materialListRepository.save(materialLists);
        return MaterialListsMapper.mapToMaterialListsDto(updatedMaterialLists);
    }

    @Override
    public MaterialListsDto deleteMaterialList(Long materialListId) {
        MaterialLists materialLists = materialListRepository.findById(materialListId)
                .orElseThrow(() -> new IllegalArgumentException("Invalid material list Id:" + materialListId));
        materialListRepository.delete(materialLists);
        return MaterialListsMapper.mapToMaterialListsDto(materialLists);
    }
}

Parts的GET返回结果

[
    {
        "id": 10002,
        "partNumber": "PN-001",
        "description": "Part Number 001",
        "manufacturer": "Manufacturer A",
        "supplier": "Supplier A",
        "serialNumber": "SN-001",
        "status": "ACTIVE",
        "category": "Category A",
        "quantityOnHand": 100,
        "storageLocation": "Location A",
        "expirationDate": "2025-12-31",
        "minQuantity": 10,
        "maxQuantity": 200,
        "cost": 10.5,
        "leadTime": 5,
        "processCriticality": "High"
    },
    {
        "id": 20004,
        "partNumber": "PN-00123",
        "description": "Description of the part",
        "manufacturer": "Manufacturer Name",
        "supplier": "Supplier Name",
        "serialNumber": "SN-00123",
        "status": "ACTIVE",
        "category": "Category Name",
        "quantityOnHand": 50,
        "storageLocation": "Location A",
        "expirationDate": "2024-12-31",
        "minQuantity": 10,
        "maxQuantity": 100,
        "cost": 25.75,
        "leadTime": 14,
        "processCriticality": "High"
    },
    {
        "id": 30002,
        "partNumber": "PN-12345",
        "description": "Sample Part 1",
        "manufacturer": "Manufacturer A",
        "supplier": "Supplier A",
        "serialNumber": "SN-12345",
        "status": "ACTIVE",
        "category": "Category A",
        "quantityOnHand": 10,
        "storageLocation": "Location A",
        "expirationDate": "2024-12-31",
        "minQuantity": 5,
        "maxQuantity": 50,
        "cost": 100.0,
        "leadTime": 7,
        "processCriticality": "High"
    }
]

尝试发送的MaterialLists POST请求

{
    "description": "Sample Material List",
    "parts": [
        {
            "partNumber": "PN-12345",
            "description": "Sample Part 1",
            "manufacturer": "Manufacturer A",
            "supplier": "Supplier A",
            "serialNumber": "SN-12345",
            "status": "ACTIVE",
            "category": "Category A",
            "quantityOnHand": 10,
            "storageLocation": "Location A",
            "expirationDate": "2024-12-31",
            "minQuantity": 5,
            "maxQuantity": 50,
            "cost": 100.0,
            "leadTime": 7,
            "processCriticality": "High"
        }
    ]
}

解决方案

1. 修复双向关联维护逻辑

错误核心是Parts的materialList字段为null,虽然你在service中手动设置了关联,但可能因为Mapper转换覆盖了实例,或者集合未初始化导致失效。改用实体类中的addPart方法确保双向关联正确:

修改MaterialLists实体,初始化集合:

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class MaterialLists {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long materialListID;

    private String description;

    @OneToMany(mappedBy = "materialList", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Parts> parts = new ArrayList<>(); // 初始化空集合

    public void addPart(Parts part) {
        parts.add(part);
        part.setMaterialList(this);
    }
}

修改service的创建逻辑:

@Override
public MaterialListsDto createMaterialList(MaterialListsDto materialListsDto) {
    MaterialLists materialLists = new MaterialLists();
    materialLists.setDescription(materialListsDto.getDescription());

    if (materialListsDto.getParts() != null) {
        for (PartsDto partDto : materialListsDto.getParts()) {
            Parts part = PartsMapper.mapToParts(partDto);
            materialLists.addPart(part); // 自动维护双向关联
        }
    }

    MaterialLists savedMaterialLists = materialListRepository.save(materialLists);
    return MaterialListsMapper.mapToMaterialListsDto(savedMaterialLists);
}

2. 调整Parts的非空约束

你的Parts实体中material_list_id被设置为nullable=false,意味着所有Parts必须属于某个MaterialLists,但你之前创建的Parts数据(如GET结果)没有关联任何物料清单,这本身违反了数据库约束。

  • 如果业务允许Parts独立存在,移除非空约束:
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "material_list_id") // 移除nullable=false
private MaterialLists materialList;
  • 如果业务要求Parts必须关联物料清单,禁止单独创建Parts,必须通过MaterialLists接口创建,并清理数据库中无关联的Parts数据。

3. 修正POST请求的意图

当前请求中传递的Part的partNumber(PN-12345)已存在于数据库,而Parts实体的partNumber设置了唯一约束,会导致冲突:

  • 若要创建新Parts并关联到新MaterialLists:确保请求中的partNumber是未使用的新值。
  • 若要关联已存在的Parts到新MaterialLists:请求仅传递Part的id,修改service逻辑查询已有Part并关联:

请求体修改为:

{
    "description": "Sample Material List",
    "parts": [
        {
            "id": 30002
        }
    ]
}

service逻辑修改:

// 注入PartsRepository
private final PartsRepository partsRepository;

@Override
public MaterialListsDto createMaterialList(MaterialListsDto materialListsDto) {
    MaterialLists materialLists = new MaterialLists();
    materialLists.setDescription(materialListsDto.getDescription());

    if (materialListsDto.getParts() != null) {
        for (PartsDto partDto : materialListsDto.getParts()) {
            Parts part = partsRepository.findById(partDto.getId())
                    .orElseThrow(() -> new IllegalArgumentException("Part not found: " + partDto.getId()));
            materialLists.addPart(part);
        }
    }

    MaterialLists savedMaterialLists = materialListRepository.save(materialLists);
    return MaterialListsMapper.mapToMaterialListsDto(savedMaterialLists);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 01:00:54