Hibernate无需加载实体向自定义关联表的ManyToMany关联添加元素
问题根因
你反序列化得到的HomeworkMaterial实体仅填充了内嵌主键id的字段,关联属性homework和material均为null。JPA的@MapsId注解需要从关联实体的主键取值生成内嵌ID,关联属性为空时就会抛出你遇到的空值错误。
空列表可以正常删除关联,是因为orphanRemoval = true配置下,清空集合直接触发关联记录删除,不需要读取关联属性的值。
可行解决方案(无需额外查询数据库)
方案1:使用JPA代理对象补全关联(推荐,适配现有代码逻辑)
JPA的getReferenceById方法只会生成对应ID的实体代理,不会发起实际的数据库查询,完全符合你不需要提前加载实体的需求,只有当关联ID不存在时,事务提交阶段才会触发外键约束异常,也满足你“数据异常直接抛错”的要求。
你只需要在调用save之前,遍历补全所有HomeworkMaterial的关联属性即可:
// 反序列化得到的Homework对象 Homework deserializedHomework = ...; Long homeworkId = deserializedHomework.getId(); // 生成Homework代理,整个循环只需要生成一次 Homework homeworkProxy = homeworkRepository.getReferenceById(homeworkId); for (HomeworkMaterial hm : deserializedHomework.getMaterials()) { // 补全homework关联 hm.setHomework(homeworkProxy); // 补全material代理,用内嵌ID里的materialId生成,无查询开销 Material materialProxy = materialRepository.getReferenceById(hm.getId().getMaterialId()); hm.setMaterial(materialProxy); // 可选:确保内嵌ID和关联ID一致 hm.setId(new HomeworkMaterialId(hm.getId().getMaterialId(), homeworkId)); } // 直接保存即可 homeworkRepository.save(deserializedHomework);
如果需要更便捷的处理,可以自定义Jackson反序列化器,遇到Material、Homework类型时自动根据ID生成代理对象,无需每次手动遍历。
方案2:直接写入关联表(性能最高)
如果你不想操作实体,也可以直接通过自定义DML语句插入关联记录,完全绕过实体关联校验:
// 在HomeworkMaterialRepository中添加自定义方法 @Modifying @Query(value = "INSERT INTO homework_material (material_id, homework_id, position) VALUES (:materialId, :homeworkId, :position)", nativeQuery = true) void batchInsert(@Param("materialId") Long materialId, @Param("homeworkId") Long homeworkId, @Param("position") int position);
你直接从请求JSON中取出对应的materialId、homeworkId、position值批量调用插入即可,不需要加载任何实体。
内容的提问来源于stack exchange,提问作者ShayShoq
相关产品推荐
相关产品推荐

