本文目录导读:

在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;
}
最佳实践建议
- 数据库唯一索引是基础保障:防止并发情况下的数据重复
- 业务层校验提供友好提示:提前发现重复,避免数据库异常
- 结合缓存提高性能:使用Redis等缓存热点数据的唯一性校验
- 处理并发竞争:使用数据库锁、分布式锁或版本号机制
- 批量操作优化:减少数据库查询次数,批量校验和保存
- 异步校验:对于非关键数据,可使用消息队列异步处理
选择合适的方案要根据具体业务场景、并发量、性能要求等因素综合考虑。