更新时对字段的重复校验

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

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 "数据重复";
}
相关推荐
阿kun要赚马内2 小时前
Qt写群聊项目(一):服务器
服务器·数据库·qt
康小庄2 小时前
SpringBoot 拦截器 (Interceptor) 与切面 (AOP):示例、作用、及适用场景
java·数据库·spring boot·后端·mysql·spring·spring cloud
小六花s2 小时前
SQL注入笔记
数据库·笔记·sql
yufuu982 小时前
Python在金融科技(FinTech)中的应用
jvm·数据库·python
OnYoung2 小时前
持续集成/持续部署(CI/CD) for Python
jvm·数据库·python
2301_822377652 小时前
高级爬虫技巧:处理JavaScript渲染(Selenium)
jvm·数据库·python
u0109272712 小时前
用Python和Twilio构建短信通知系统
jvm·数据库·python
闻哥2 小时前
从 SQL 执行到优化器内核:MySQL 性能调优核心知识点解析
java·jvm·数据库·spring boot·sql·mysql·面试
鹿角片ljp2 小时前
动态SQL实现模糊查询
数据库·sql·oracle