伪代码:
@Transactional(rollbackFor = Exception.class)public void add(User user) {String key = "key";RLock lock = redissonClient.getLock(key);lock.lock();try {long count = userMapper.selectCount(user);if (count == 0) {userMapper.insert(user);}} catch (Exception e) {log.error("add user error", e);} finally {lock.unlock();}System.out.println("插入成功");}
问题分析:
如果有两个线程a,b。如果a线程释放锁后,退出方法前,让出时间片,由于方法未执行完,此时事务没有提交,那么b线程在去数据库查询的时候仍然出来count为0,执行了insert操作,两个线程执行完该方法提交事务,此时数据库中会增加两条数据,幂等性失效。
注:a事务未提交,查出来count仍然为0的原因是,mysql默认的事物隔离级别是可重复读,因此无法读取为提交的数据
解决方式:
第一种:
//@Transactional(rollbackFor = Exception.class)public void add(User user) {String key = "key";RLock lock = redissonClient.getLock(key);lock.lock();try {long count = userMapper.selectCount(user);if (count == 0) {userMapper.insert(user);}} catch (Exception e) {log.error("add user error", e);} finally {lock.unlock();}System.out.println("插入成功");}
去掉@Transactional注解,这样在a线程在insert时候就会自动提交事务,a释放锁后,b在查询时候,count不等0,不执行插入操作。
方法二:
public class Test {@Resourceprivate UserMapper userMapper;@Resourceprivate UserService userService;@Resourceprivate RedissonClient redissonClient;public void test(User user) {String key = "key";RLock lock = redissonClient.getLock(key);lock.lock();try {userService.addOk(user);} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}@Transactional(rollbackFor = Exception.class)public void addOk(User user) {long count = userMapper.selectCount(user);if (count == 0) {userMapper.insert(user);}}
}
a提交事务后再释放锁后,b在查询时候,count不等0,不执行插入操作。
绝大数情况下,为了防止重复数据的产生,我们都会在表中加唯一索引,这是一个非常简单,并
且有效的方案。 虽说抛异常对数据来说没有影响,不会造成错误数据。但是为了保证接口幂等性,我们需要对该 异常进行捕获,然后返回成功。