一、数据库约束、捕获异常
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 "数据重复";
}