更新时对字段的重复校验

一、数据库约束、捕获异常

ALTER TABLE student ADD UNIQUE KEY uk_student_no (student_no);

防止并发重复插入

数据库层面保证数据一致性

二、业务层查询校验

java 复制代码
 @Service
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements StudentService {
    //添加学生
    public String addStudentWithCheck(Student student) {
        // 1. 学号重复校验(插入时不需要排除ID)
        if (isStudentNoExists(student.getStudentNo(), null)) {
            return "学号已存在";
        }
        
        // 2. 手机号重复校验(插入时不需要排除ID)
        if (isPhoneExists(student.getPhone(), null)) {
            return "手机号已存在";
        }
        
        // 3. 执行插入
        boolean success = this.save(student);
        return success ? "添加成功" : "添加失败";
    }
    
    private boolean isStudentNoExists(String studentNo, Long excludeId) {
        QueryWrapper<Student> wrapper = new QueryWrapper<>();
        wrapper.eq("student_no", studentNo);
        
        // 更新时:排除当前记录
        if (excludeId != null) {
            wrapper.ne("id", excludeId);
        }
        
        return this.count(wrapper) > 0;
    }   
 }

三、使用 MyBatis-Plus 的 saveOrUpdate

java 复制代码
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> 
        implements StudentService {
    
    /**
     * 方式三:使用 saveOrUpdate(根据学号判断)
     * 注意:这里以学号作为唯一判断依据
     */
    @Override
    public String saveOrUpdateByStudentNo(Student student) {
        // 1. 检查学号是否存在
        QueryWrapper<Student> wrapper = new QueryWrapper<>();
        wrapper.eq("student_no", student.getStudentNo());
        
        Student existing = this.getOne(wrapper);
        
        // 2. 如果存在,设置ID执行更新;不存在,执行插入
        if (existing != null) {
            // 保留原ID,更新其他字段
            student.setId(existing.getId());
            // 注意:这里不会校验手机号重复!
        }
        
        // 3. 执行保存或更新
        boolean success = this.saveOrUpdate(student);
        return success ? "操作成功" : "操作失败";
    }
}
@RestController
@RequestMapping("/student")
@Validated  // 启用参数校验
public class StudentController {
    
    @Autowired
    private StudentService studentService;
    
    /**
     * 方式三:使用自定义注解校验
     */
    @PostMapping("/add-with-annotation")
    public String addWithAnnotation(@Valid @RequestBody StudentDTO dto) {
        // DTO 转 Entity
        Student student = new Student();
        BeanUtils.copyProperties(dto, student);
        
        // 调用保存
        boolean success = studentService.save(student);
        return success ? "添加成功" : "添加失败";
    }
    
    /**
     * 方式三:使用 saveOrUpdate 方法
     */
    @PostMapping("/save-or-update")
    public String saveOrUpdate(@RequestBody Student student) {
        return studentService.saveOrUpdateByStudentNo(student);
    }
}

四、自定义注解校验

java 复制代码
//创建自定义注解
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UniqueValidator.class) //用于指定校验逻辑的实现类
public @interface UniqueField {
    
    String message() default "字段值已存在";
    
    Class<?>[] groups() default {};
    
    Class<? extends Payload>[] payload() default {};
    
    // 表名
    String tableName();
    
    // 字段名
    String column();
    
    // 排除的ID(用于更新)
    String excludeId() default "";
}
//创建校验器
public class UniqueValidator implements ConstraintValidator<UniqueField, Object> {
    
    @Autowired
    private StudentMapper studentMapper;
    
    private String tableName;
    private String column;
    private String excludeId;
    
    @Override
    public void initialize(UniqueField constraintAnnotation) {
        this.tableName = constraintAnnotation.tableName();
        this.column = constraintAnnotation.column();
        this.excludeId = constraintAnnotation.excludeId();
    }
    
    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        if (value == null) {
            return true;
        }
        
        // 构建查询条件
        QueryWrapper<Student> wrapper = new QueryWrapper<>();
        wrapper.eq(column, value.toString());
        
        // 如果排除ID不为空,添加排除条件
        if (excludeId != null && !excludeId.isEmpty()) {
            wrapper.ne("id", Long.parseLong(excludeId));
        }
        
        // 查询是否存在
        return studentMapper.selectCount(wrapper) == 0;
    }
}
//修改实体类
@TableName("student")
public class Student {
    @UniqueField(
        tableName = "student", 
        column = "student_no",
        message = "学号已存在"
    )
    private String studentNo;
}
//创建 DTO(用于接收参数)
public class StudentDTO {
    private Long id;  // 更新时需要
    
    @NotBlank(message = "学号不能为空")
    @UniqueField(
        tableName = "student", 
        column = "student_no",
        excludeId = "#{id}",  // 动态获取ID
        message = "学号已存在"
    )
    private String studentNo;
}
//Controller 层使用
@RestController
@RequestMapping("/student")
@Validated  // 启用参数校验
public class StudentController {
    
    @Autowired
    private StudentService studentService;
    
    /**
     * 方式三:使用自定义注解校验
     */
    @PostMapping("/add-with-annotation")
    public String addWithAnnotation(@Valid @RequestBody StudentDTO dto) {
        // DTO 转 Entity
        Student student = new Student();
        BeanUtils.copyProperties(dto, student);
        
        // 调用保存
        boolean success = studentService.save(student);
        return success ? "添加成功" : "添加失败";
    }
}

五、Redis 分布式锁防止并发重复

java 复制代码
@Service
public class UserService {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    @Transactional
    public boolean saveWithLock(User user) {
        String lockKey = "user:duplicate_check:" + user.getUsername();
        String requestId = UUID.randomUUID().toString();
        
        try {
            // 获取分布式锁
            Boolean locked = redisTemplate.opsForValue()
                .setIfAbsent(lockKey, requestId, 10, TimeUnit.SECONDS);
            
            if (!Boolean.TRUE.equals(locked)) {
                throw new BusinessException("操作过于频繁");
            }
            
            // 检查重复
            LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery();
            wrapper.eq(User::getUsername, user.getUsername());
            if (this.count(wrapper) > 0) {
                throw new BusinessException("用户名已存在");
            }
            
            return this.save(user);
        } finally {
            // 释放锁
            if (requestId.equals(redisTemplate.opsForValue().get(lockKey))) {
                redisTemplate.delete(lockKey);
            }
        }
    }
}

六、AOP 切面统一处理

java 复制代码
@Aspect
@Component
public class DuplicateCheckAspect {
    
    @Autowired
    private UserService userService;
    
    @Around("@annotation(checkDuplicate)")
    public Object checkDuplicate(ProceedingJoinPoint joinPoint, 
            CheckDuplicate checkDuplicate) throws Throwable {
        
        Object[] args = joinPoint.getArgs();
        if (args.length > 0 && args[0] instanceof User) {
            User user = (User) args[0];
            
            // 执行校验逻辑
            if (userService.existsDuplicate(user)) {
                throw new BusinessException(checkDuplicate.message());
            }
        }
        
        return joinPoint.proceed();
    }
}

// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckDuplicate {
    String message() default "数据重复";
}
相关推荐
全栈老石1 小时前
拆解低代码引擎核心:元数据驱动的"万能表"架构
数据库·低代码
倔强的石头_20 小时前
kingbase备份与恢复实战(二)—— sys_dump库级逻辑备份与恢复(Windows详细步骤)
数据库
jiayou642 天前
KingbaseES 实战:深度解析数据库对象访问权限管理
数据库
李广坤3 天前
MySQL 大表字段变更实践(改名 + 改类型 + 改长度)
数据库
爱可生开源社区4 天前
2026 年,优秀的 DBA 需要具备哪些素质?
数据库·人工智能·dba
随逸1774 天前
《从零搭建NestJS项目》
数据库·typescript
加号35 天前
windows系统下mysql多源数据库同步部署
数据库·windows·mysql
シ風箏5 天前
MySQL【部署 04】Docker部署 MySQL8.0.32 版本(网盘镜像及启动命令分享)
数据库·mysql·docker
李慕婉学姐5 天前
Springboot智慧社区系统设计与开发6n99s526(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
百锦再5 天前
Django实现接口token检测的实现方案
数据库·python·django·sqlite·flask·fastapi·pip