SpringBoot + Mybatis-Plus 并发更新数据丢失问题解决方法 (无乐观锁)
SpringBoot + Mybatis-Plus 并发更新数据丢失问题解决方法 (无乐观锁)
在使用 SpringBoot 和 Mybatis-Plus 进行数据库操作时,如果多个事务并发更新同一行数据,可能会导致更新丢失的问题,即最后一次更新的结果覆盖了之前的所有更新结果,造成数据不一致。
问题描述
例如,两个线程同时更新同一个用户的年龄,第一个线程将年龄更新为 20,第二个线程将年龄更新为 30,最终数据库中用户的年龄可能只被更新为 30,第一个线程的更新结果丢失。
解决方法
在不使用乐观锁的情况下,可以使用数据库的悲观锁机制来解决并发更新的问题。具体实现方式如下:
-
在 SQL 语句中使用
FOR UPDATE关键字,对需要更新的行进行加锁。 -
在代码中使用
@Transactional注解开启事务,并在需要更新数据的方法上添加synchronized关键字,保证同一时刻只有一个线程能够访问该方法。 -
在更新数据时,先查询出需要更新的数据,并判断数据是否已被其他事务更新过,如果已被更新,则回滚事务重新尝试更新,直到更新成功为止。
-
在更新数据时,需要手动提交事务,以释放悲观锁。
代码实现
- SQL 语句中使用
FOR UPDATE关键字:
UPDATE table_name SET column1 = value1 WHERE id = ? FOR UPDATE
- 代码中使用
@Transactional注解和synchronized关键字:
@Service
@Transactional(rollbackFor = Exception.class)
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public synchronized void updateUser(User user) {
// 查询需要更新的数据,并加锁
User dbUser = userMapper.selectById(user.getId());
// 判断数据是否已被其他事务更新过
if (dbUser.getVersion() > user.getVersion()) {
throw new BusinessException('数据已被其他事务更新,请重试');
}
// 更新数据
user.setVersion(user.getVersion() + 1);
userMapper.updateById(user);
// 手动提交事务,释放悲观锁
TransactionAspectSupport.currentTransactionStatus().flush();
}
}
- 控制器中调用更新方法:
@RestController
@RequestMapping('/user')
public class UserController {
@Autowired
private UserService userService;
@PutMapping
public Result updateUser(@RequestBody User user) {
userService.updateUser(user);
return Result.success();
}
}
完整代码
UserMapper.java
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
User.java
@Data
@TableName('user')
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String password;
private Integer age;
private Integer version;
}
UserService.java
public interface UserService {
void updateUser(User user);
}
UserServiceImpl.java
@Service
@Transactional(rollbackFor = Exception.class)
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public synchronized void updateUser(User user) {
// 查询需要更新的数据,并加锁
User dbUser = userMapper.selectById(user.getId());
// 判断数据是否已被其他事务更新过
if (dbUser.getVersion() > user.getVersion()) {
throw new BusinessException('数据已被其他事务更新,请重试');
}
// 更新数据
user.setVersion(user.getVersion() + 1);
userMapper.updateById(user);
// 手动提交事务,释放悲观锁
TransactionAspectSupport.currentTransactionStatus().flush();
}
}
UserController.java
@RestController
@RequestMapping('/user')
public class UserController {
@Autowired
private UserService userService;
@PutMapping
public Result updateUser(@RequestBody User user) {
userService.updateUser(user);
return Result.success();
}
}
总结
通过使用悲观锁机制,可以有效地解决 SpringBoot 和 Mybatis-Plus 项目中并发更新数据导致的更新丢失问题,确保数据的一致性。
注意:
- 使用悲观锁会降低并发性能,如果并发量较大,建议使用乐观锁。
- 在使用悲观锁时,需要确保手动提交事务,以释放锁。
- 以上代码示例仅供参考,实际应用中需要根据具体情况进行调整。
原文地址: https://www.cveoy.top/t/topic/nCPC 著作权归作者所有。请勿转载和采集!