悲观锁和乐观锁介绍和实现

115 阅读3分钟

悲观锁和乐观锁介绍和实现

摘要:本文介绍了悲观锁和乐观锁的概念、使用场景及实现方式。悲观锁通过加锁保证数据一致性,适用于金融交易等高并发场景;乐观锁通过版本号控制,适合读多写少的系统。文章详细展示了基于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)");
        }
    }
}