悲观锁和乐观锁介绍和实现
摘要:本文介绍了悲观锁和乐观锁的概念、使用场景及实现方式。悲观锁通过加锁保证数据一致性,适用于金融交易等高并发场景;乐观锁通过版本号控制,适合读多写少的系统。文章详细展示了基于MyBatis Plus的实现方案,包括实体类配置(使用@Version注解)、拦截器配置、两种锁的Service层实现(自动处理版本号或手动加锁)以及Controller层示例。最后提供了乐观锁和悲观锁的完整代码实现,帮助开发者在不同业务场景中选择合适的并发控制策略。
一.产生背景
随着多用户并发访问共享数据的场景增加,为了保证数据的一致性。
二.概念
悲观锁:用户在对共享数据进行操作之前,对共享数据进行加锁,确保同一时间内只有一个线程可以访问到到该数据。
使用场景:对数据一致性要求高,比如:金融交易系统转账,库存扣减/秒杀。
乐观锁:用户在对共享数据进行操作之前,先记录共享数据的版本号,并修改共享数据时对比版本号是否一致,只有版本号一致才允许数据被修改。
使用场景:系统吞吐量要求高的系统读多写少,比如:评论,点赞,社交网络。
三.代码实现
1.项目结构
src/main/java
└── com/example/demo
├── config
│ └── MyBatisPlusConfig.java # MyBatis Plus 配置
├── entity
│ └── User.java # 实体类(含乐观锁字段)
├── mapper
│ └── UserMapper.java # Mapper 接口
├── service
│ ├── impl
│ │ └── UserServiceImpl.java # 服务实现类
│ └── UserService.java # 服务接口
└── controller
└── UserController.java # 控制器
2.实体类
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;
@Data
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
// 乐观锁字段(关键!)
@Version
private Integer version;
}
3.MyBatis Plus 配置 (MyBatisPlusConfig.java)
[!CAUTION]
注:乐观锁插件MyBatis Plus 自动处理版本号,拦截器中判断是否有@Version 注解
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
4.Mapper层(UserMapper.java)
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
public interface UserMapper extends BaseMapper<User> {
/**
* 悲观锁查询:SELECT ... FOR UPDATE
* @param id 用户ID
* @return 用户对象
*/
@Select("SELECT * FROM user WHERE id = #{id} FOR UPDATE")
User selectForUpdate(Long id);
/**
* 悲观锁更新
* @param id 用户ID
* @param name 新名称
* @return 更新行数
*/
@Update("UPDATE user SET name = #{name} WHERE id = #{id}")
int updateNameForPessimisticLock(@Param("id") Long id, @Param("name") String name);
}
5.Service层(UserService.java & UserServiceImpl.java)
// UserService.java
public interface UserService {
boolean updateNameWithOptimisticLock(Long id, String newName);
boolean updateNameWithPessimisticLock(Long id, String newName);
}
// UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
@Transactional
public boolean updateNameWithOptimisticLock(Long id, String newName) {
// 1. 查询用户(自动带版本号)
User user = userMapper.selectById(id);
if (user == null) {
return false;
}
// 2. 更新数据
user.setName(newName);
// 3. 执行更新(MyBatis Plus 自动处理版本号)
int result = userMapper.updateById(user);
return result > 0;
}
@Override
@Transactional
public boolean updateNameWithPessimisticLock(Long id, String newName) {
// 1. 悲观锁查询(加锁)
User user = userMapper.selectForUpdate(id);
if (user == null) {
return false;
}
// 2. 更新数据
user.setName(newName);
// 3. 执行更新(数据库行锁已确保一致性)
int result = userMapper.updateNameForPessimisticLock(id, newName);
return result > 0;
}
}
6.Controller层(UserController.java)
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@PutMapping("/{id}/name")
public ResponseEntity<?> updateName(
@PathVariable Long id,
@RequestParam String newName) {
// 乐观锁更新(推荐在读多写少场景使用)
boolean success = userService.updateNameWithOptimisticLock(id, newName);
if (success) {
return ResponseEntity.ok("Name updated successfully");
} else {
return ResponseEntity.status(HttpStatus.CONFLICT)
.body("Conflict: Name update failed (concurrent modification)");
}
}
}