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
相关产品推荐
相关产品推荐

