调用Repository.saveAll抛出InvalidDataAccessApiUsageException异常如何解决?
异常根因
反序列化Bugzilla接口响应时,若同一个BugUser(用户ID相同)同时出现在经办人字段assigned_to_detail和抄送列表cc_detail中,Spring RestTemplate会生成两个内存地址不同、但主键ID完全一致的BugUser游离对象。JPA执行merge操作时,无法处理同一个主键对应多个游离实例的场景,因此抛出冲突异常。当你注释掉任意一个关联属性后,同一个ID的BugUser只会有一个实例,自然不会触发冲突。
解决方案
1. 修正不合理的级联配置
BugUser属于独立的主数据实体,不应该跟随Bug对象级联全量操作,删除两个关联注解中的cascade = CascadeType.ALL配置即可,调整后代码如下:
@Entity @Table public class Bug { // 注意Java类名规范建议用大驼峰 // .... @ManyToOne (fetch = FetchType.EAGER) @JoinColumn (name = "assigned_to_user_id") @SerializedName ("assigned_to_detail") protected BugUser assignedTo; // 注意:@ManyToMany不能用@JoinColumn映射,需要用@JoinTable @ManyToMany @JoinTable( name = "bug_cc_rel", joinColumns = @JoinColumn(name = "bug_id"), inverseJoinColumns = @JoinColumn(name = "user_id") ) @SerializedName ("cc_detail") protected List<BugUser> cc; // ... }
2. 反序列化后统一替换相同主键的BugUser实例
拿到RestTemplate反序列化后的Bug对象后,先按主键去重所有关联的BugUser,保证同一个主键的BugUser在整个对象结构中是同一个实例:
// 按主键去重收集所有关联的BugUser Map<Long, BugUser> userMap = new HashMap<>(); if (bug.getAssignedTo() != null) { userMap.put(bug.getAssignedTo().getId(), bug.getAssignedTo()); } if (bug.getCc() != null) { bug.getCc().forEach(ccUser -> userMap.putIfAbsent(ccUser.getId(), ccUser)); // 替换抄送列表的实例为统一实例 bug.setCc(bug.getCc().stream() .map(u -> userMap.get(u.getId())) .collect(Collectors.toList())); } // 替换经办人实例为统一实例 if (bug.getAssignedTo() != null) { bug.setAssignedTo(userMap.get(bug.getAssignedTo().getId())); }
3. 持久化前先处理BugUser为托管态
如果需要同步更新用户信息,可先将去重后的BugUser批量持久化,拿到托管态实例后再赋值回Bug对象,最后持久化Bug即可:
// 先批量持久化所有去重后的用户 List<BugUser> savedUsers = bugUserRepository.saveAll(userMap.values()); Map<Long, BugUser> savedUserMap = savedUsers.stream() .collect(Collectors.toMap(BugUser::getId, Function.identity())); // 替换Bug中的用户引用为托管态实例 bug.setAssignedTo(savedUserMap.get(bug.getAssignedTo().getId())); bug.setCc(bug.getCc().stream() .map(u -> savedUserMap.get(u.getId())) .collect(Collectors.toList())); // 最后持久化Bug bugRepository.save(bug);
内容的提问来源于stack exchange,提问作者TheQuestionIs
相关产品推荐
相关产品推荐

