分析以下案例代码问题:
@Slf4j
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
@Resource
private RedissonClient redissonClient;
/**
* 接口需要幂等,userId不允许重复
*
* @param user
*/
@Override
// 开启事务,异常回滚
@Transactional(rollbackFor = Exception.class)
public void addUser(User user) {
log.info("add user params user:{}", JSON.toJSONString(user));
// 1.校验参数userId
Assert.isTrue(StringUtils.isNotBlank(user.getUserId()), "userId不允许null");
// 2.获取redis分布式锁;拿到后加锁处理
String key = String.format("userId_%d", user.getUserId());
RLock lock = redissonClient.getLock(key);
lock.lock();
try {
// 3.数据库查询userId用户存不存在
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.eq(User::getUserId, user.getUserId());
long count = userMapper.selectCount(wrapper);
// 4.若user表查询用户id不存在,进行新增用户操作
if (count == 0) {
userMapper.insert(user);
}
} catch (Exception e) {
log.error("add user error", e);
} finally {
// 5.释放分布式锁
lock.unlock();
}
System.out.println("并发执行,同时插入了N条");
}
}
方法逻辑:
主要问题:
1.redis
锁使用问题:redis
锁在并发下并没有保证每个事务都进行提交!
2.在并发下可以在数据库中插入多条记录!redis
锁没有保证事务原子性,使接口幂等性失效!
**解决方案一:**将事务放在redis
锁内(保证事务原子性),从而保证并发下新增用户查询到之前每个事务提交后新增的用户;
方法逻辑(修正):
@Slf4j
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
@Resource
private RedissonClient redissonClient;
/**
* 接口需要幂等,userId不允许重复
* @param user
*/
@Override
public void addUser(User user) {
log.info("add user params user:{}", JSON.toJSONString(user));
// 1.校验参数userId
Assert.isTrue(StringUtils.isNotBlank(user.getUserId()), "userId不允许null");
// 2.获取redis分布式锁;拿到后加锁处理
String key = String.format("userId_%d", user.getUserId());
RLock lock = redissonClient.getLock(key);
lock.lock();
try {
// 3.调用执行查询新增用户方法;该方法内开启事务;保证事务完整性
doAddUser(user)
} catch (Exception e) {
log.error("add user error", e);
} finally {
// 4.释放分布式锁
lock.unlock();
}
System.out.println("并发执行,同时插入了1条");
}
@Override
// 开启事务
@Transactional(rollbackFor = Exception.class)
public void doAddUser(User user) {
// 查询用户
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.eq(User::getUserId, user.getUserID());
long count = userMapper.selectCount(wrapper);
// 查询用户不存在,进行新增
if (count == 0) {
userMapper.insert(user);
}
}
}
方案二:不开启事务;可以保证并发下接口幂等:(原因: mysql默认隔离级别:读已经提交;所以每次新增会自动提交事务(事务内原子性操作),从而保证接口幂等)
@Slf4j
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
@Resource
private RedissonClient redissonClient;
/**
* 接口需要幂等,userId不允许重复
*
* @param user
*/
@Override
// 不开启事务,使用mysql默认事务:读已提交
// @Transactional(rollbackFor = Exception.class)
public void addUser(User user) {
log.info("add user params user:{}", JSON.toJSONString(user));
// 1.校验参数userId
Assert.isTrue(StringUtils.isNotBlank(user.getUserId()), "userId不允许null");
// 2.获取redis分布式锁;拿到后加锁处理
String key = String.format("userId_%d", user.getUserId());
RLock lock = redissonClient.getLock(key);
lock.lock();
try {
// 3.数据库查询userId用户存不存在
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.eq(User::getUserId, user.getUserId());
long count = userMapper.selectCount(wrapper);
// 4.若user表查询用户id不存在,进行新增用户操作
if (count == 0) {
userMapper.insert(user);
}
} catch (Exception e) {
log.error("add user error", e);
} finally {
// 5.释放分布式锁
lock.unlock();
}
System.out.println("并发执行,同时插入了N条");
}
}