Java案例如何实现唯一性校验?

wen java案例 69

本文目录导读:

Java案例如何实现唯一性校验?

  1. 数据库层面 - 唯一索引
  2. 业务逻辑层校验
  3. 使用Spring Validation注解
  4. 处理并发情况 - 乐观锁
  5. 完整示例 - 批量校验
  6. 最佳实践建议

在Java中实现唯一性校验通常需要结合数据库和业务逻辑来处理,以下是几种常见的实现方案:

数据库层面 - 唯一索引

创建唯一索引

-- 单字段唯一索引
CREATE UNIQUE INDEX idx_user_email ON users(email);
-- 复合唯一索引
CREATE UNIQUE INDEX idx_user_email_phone ON users(email, phone);

MyBatis-Plus 注解方式

@Entity
@Table(name = "users", uniqueConstraints = {
    @UniqueConstraint(columnNames = {"email"}),
    @UniqueConstraint(columnNames = {"phone"})
})
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(unique = true)
    private String email;
    private String phone;
}

业务逻辑层校验

Spring Boot + JPA 实现

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    public User createUser(User user) {
        // 先查询是否存在
        if (userRepository.existsByEmail(user.getEmail())) {
            throw new BusinessException("邮箱已存在");
        }
        if (userRepository.existsByPhone(user.getPhone())) {
            throw new BusinessException("手机号已存在");
        }
        return userRepository.save(user);
    }
}
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    boolean existsByEmail(String email);
    boolean existsByPhone(String phone);
    Optional<User> findByEmail(String email);
}

MyBatis 实现

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    public int createUser(User user) {
        // 业务层校验
        User existingUser = userMapper.findByEmail(user.getEmail());
        if (existingUser != null) {
            throw new BusinessException("邮箱已存在");
        }
        return userMapper.insert(user);
    }
}
@Mapper
public interface UserMapper {
    User findByEmail(@Param("email") String email);
    int insert(User user);
}

使用Spring Validation注解

自定义验证注解

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UniqueEmailValidator.class)
public @interface UniqueEmail {
    String message() default "邮箱已被注册";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {
    @Autowired
    private UserRepository userRepository;
    @Override
    public boolean isValid(String email, ConstraintValidatorContext context) {
        return email != null && !userRepository.existsByEmail(email);
    }
}

使用在控制器中

@RestController
@RequestMapping("/users")
public class UserController {
    @PostMapping
    public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
        // 业务逻辑
    }
}
public class User {
    @NotNull
    @UniqueEmail
    private String email;
    @NotNull
    @Size(min = 11, max = 11)
    @Pattern(regexp = "^1[3-9]\\d{9}$")
    private String phone;
}

处理并发情况 - 乐观锁

使用版本号

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String email;
    @Version
    private Integer version; // 乐观锁版本号
}
@Service
public class UserService {
    public User createUserWithLock(User user) {
        try {
            return userRepository.save(user);
        } catch (OptimisticLockException e) {
            throw new BusinessException("数据已被修改,请重试");
        }
    }
}

使用分布式锁

@Service
public class UserService {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    public User createUserWithDistributedLock(User user) {
        String lockKey = "user:email:" + user.getEmail();
        Boolean locked = redisTemplate.opsForValue()
                .setIfAbsent(lockKey, "locked", 10, TimeUnit.SECONDS);
        if (!Boolean.TRUE.equals(locked)) {
            throw new BusinessException("邮箱已被注册");
        }
        try {
            // 双重检查
            if (userRepository.existsByEmail(user.getEmail())) {
                throw new BusinessException("邮箱已存在");
            }
            return userRepository.save(user);
        } finally {
            redisTemplate.delete(lockKey);
        }
    }
}

完整示例 - 批量校验

@Slf4j
@Service
public class BatchValidationService {
    @Autowired
    private UserRepository userRepository;
    /**
     * 批量导入用户时进行唯一性校验
     */
    public BatchImportResult batchImportUsers(List<User> users) {
        BatchImportResult result = new BatchImportResult();
        List<String> emails = users.stream()
                .map(User::getEmail)
                .collect(Collectors.toList());
        // 批量查询已存在的数据
        List<User> existingUsers = userRepository.findByEmailIn(emails);
        Set<String> existingEmails = existingUsers.stream()
                .map(User::getEmail)
                .collect(Collectors.toSet());
        List<User> validUsers = new ArrayList<>();
        List<String> duplicateEmails = new ArrayList<>();
        for (User user : users) {
            if (existingEmails.contains(user.getEmail())) {
                duplicateEmails.add(user.getEmail());
                log.warn("邮箱 {} 已存在", user.getEmail());
            } else {
                validUsers.add(user);
            }
        }
        // 批量保存
        if (!validUsers.isEmpty()) {
            userRepository.saveAll(validUsers);
        }
        result.setSuccessCount(validUsers.size());
        result.setDuplicateCount(duplicateEmails.size());
        result.setDuplicateEmails(duplicateEmails);
        return result;
    }
}
@Data
public class BatchImportResult {
    private int successCount;
    private int duplicateCount;
    private List<String> duplicateEmails;
}

最佳实践建议

  1. 数据库唯一索引是基础保障:防止并发情况下的数据重复
  2. 业务层校验提供友好提示:提前发现重复,避免数据库异常
  3. 结合缓存提高性能:使用Redis等缓存热点数据的唯一性校验
  4. 处理并发竞争:使用数据库锁、分布式锁或版本号机制
  5. 批量操作优化:减少数据库查询次数,批量校验和保存
  6. 异步校验:对于非关键数据,可使用消息队列异步处理

选择合适的方案要根据具体业务场景、并发量、性能要求等因素综合考虑。

抱歉,评论功能暂时关闭!