当向数据库插入数据时,可能会遇到主键或唯一约束冲突导致的重复数据问题。需要一种既能有效防止重复插入,又能给用户友好提示的解决方案。
public boolean insertUser(User user) {
// 先检查是否存在
if (userDao.existsByUsername(user.getUsername())) {
throw new BusinessException("用户名已存在");
}
// 不存在则插入
return userDao.save(user) > 0;
}
优点: - 主动检查,避免异常处理 - 可自定义错误消息 - 性能影响小(查询通常比异常处理快)
public boolean insertUser(User user) {
try {
return userDao.save(user) > 0;
} catch (DuplicateKeyException e) {
throw new BusinessException("用户名已存在");
}
}
适用场景: - 高并发环境(避免查询和插入之间的竞争条件) - 复杂约束(如多字段联合唯一约束)
-- MySQL的INSERT IGNORE
INSERT IGNORE INTO users (username, email) VALUES (?, ?);
-- ON DUPLICATE KEY UPDATE
INSERT INTO users (username, email) VALUES (?, ?)
ON DUPLICATE KEY UPDATE username = VALUES(username);
Java实现:
@Insert("INSERT IGNORE INTO users (username, email) VALUES (#{username}, #{email})")
int insertUserIgnore(User user);
public boolean insertUserWithLock(User user) {
// 使用分布式锁
String lockKey = "user:insert:" + user.getUsername();
try {
if (redisLock.tryLock(lockKey, 10, TimeUnit.SECONDS)) {
if (userDao.existsByUsername(user.getUsername())) {
throw new BusinessException("用户名已存在");
}
return userDao.save(user) > 0;
}
throw new BusinessException("系统繁忙,请稍后再试");
} finally {
redisLock.unlock(lockKey);
}
}
前端防抖:在用户输入时进行实时校验
// 使用lodash的debounce
const checkUsername = _.debounce(async (username) => {
const { data } = await axios.get(`/api/user/check?username=${username}`);
setMessage(data.exists ? "用户名已存在" : "");
}, 500);
后端缓存校验结果:短时间内相同请求直接返回缓存结果
@Cacheable(value = "userCheck", key = "#username")
public boolean checkUsernameExists(String username) {
return userDao.existsByUsername(username);
}
幂等设计:为操作生成唯一ID,重复请求直接返回之前的结果
@PostMapping("/users")
public ResponseEntity<?> createUser(@RequestHeader("X-Request-ID") String requestId,
@RequestBody User user) {
if (redisTemplate.hasKey(requestId)) {
return ResponseEntity.ok().build();
}
// 处理业务逻辑
redisTemplate.opsForValue().set(requestId, "processed", 24, TimeUnit.HOURS);
return ResponseEntity.ok().build();
}
选择哪种方案取决于您的具体业务场景、性能要求和并发级别。