Java使用foreach循环批量保存用户列表到数据库报错如何解决
问题原因分析
- 数据库层脏数据触发查询异常:报错信息里的
returned non unique result明确说明,调用findByEmail时数据库中对应email已经存在多条重复记录。findByEmail默认要求返回最多1条匹配结果,多条匹配时会直接抛出该异常,和你的业务判断逻辑无关。 - 核心业务逻辑写反:你当前用的
orElseThrow是Optional为空(即数据库没查到该用户)时才抛出“用户已存在”异常,查到已存在的用户反而会往下执行添加逻辑,完全和“存在则抛错、不存在才保存”的需求相反。 - 缺少两层必要校验:既没有校验传入的请求列表里是否有重复email,也没有在数据库层给email加唯一索引,很容易产生脏数据和并发重复插入问题。
解决步骤
- 清理脏数据并添加数据库唯一约束
先把数据库中重复email的历史脏数据删除,之后给User实体的email字段加唯一约束,JPA写法如下:
@Column(unique = true, nullable = false) private String email;
数据库层面的唯一约束是兜底方案,避免任何场景下出现重复email的记录。
- 修正业务判断逻辑,补全入参校验
修正后的代码如下:
public List<User> addUsers(@RequestBody List<User> userList) { // 先校验请求参数中是否存在重复email Set<String> requestEmails = new HashSet<>(); userList.forEach(user -> { String email = user.getEmail(); if (!requestEmails.add(email)) { throw new ResourceAlreadyExistsException("User with ", "email already exists in request.", email); } }); List<User> toSaveUsers = new ArrayList<>(); userList.forEach(user -> { // 存在用户就抛异常,不存在才加入待保存列表 if (userRepository.findByEmail(user.getEmail()).isPresent()) { throw new ResourceAlreadyExistsException("User with ", "email already exists.", user.getEmail()); } toSaveUsers.add(user); }); return userRepository.saveAll(toSaveUsers); }
- 高并发场景优化
如果接口并发量较高,就算加了代码校验也可能出现并发插入冲突,可以额外捕获数据库抛出的唯一约束冲突异常,转换成自定义的ResourceAlreadyExistsException返回即可,不需要额外加锁降低性能。
内容的提问来源于stack exchange,提问作者user9347049
相关产品推荐
相关产品推荐

