更新时对字段的重复校验

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

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 "数据重复";
}
相关推荐
dovens1 分钟前
PostgreSQL 中进行数据导入和导出
大数据·数据库·postgresql
IOT.FIVE.NO.11 分钟前
claude code desktop cowork报错解决和记录Workspace..The isolated Linux environment ...
linux·服务器·数据库
Rick199310 分钟前
mysql 慢查询怎么快速定位
android·数据库·mysql
科技小花7 小时前
全球化深水区,数据治理成为企业出海 “核心竞争力”
大数据·数据库·人工智能·数据治理·数据中台·全球化
X56618 小时前
如何在 Laravel 中正确保存嵌套动态表单数据(主服务与子服务)
jvm·数据库·python
虹科网络安全9 小时前
艾体宝干货|数据复制详解:类型、原理与适用场景
java·开发语言·数据库
2301_7717172110 小时前
解决mysql报错:1406, Data too long for column
android·数据库·mysql
小江的记录本10 小时前
【Kafka核心】架构模型:Producer、Broker、Consumer、Consumer Group、Topic、Partition、Replica
java·数据库·分布式·后端·搜索引擎·架构·kafka
dvjr cloi10 小时前
MySQL Workbench菜单汉化为中文
android·数据库·mysql
dFObBIMmai11 小时前
MySQL主从同步中大事务导致的延迟_如何拆分大事务优化同步
jvm·数据库·python