SpringBoot + MyBatis-Plus 并发更新同一行数据导致更新丢失的解决方案:悲观锁

在 SpringBoot 项目中,使用 MyBatis-Plus 进行数据操作时,如果多个线程并发更新同一行数据,可能会出现更新丢失的问题。这是因为多个线程可能读取到同一份数据,并在修改数据后进行更新操作,导致后更新覆盖了之前的更新。

本文介绍使用 MyBatis-Plus 的悲观锁机制来解决此问题,保证并发更新同一行数据时的数据一致性。

悲观锁

悲观锁是一种比较保守的加锁机制,它假设数据会发生冲突,所以在操作数据之前先获取锁,只有获取到锁才能对数据进行操作。如果其他线程想要操作同一份数据,就需要等待锁释放。

MyBatis-Plus 提供了 selectForUpdate 方法来实现悲观锁,通过在 SQL 语句中添加 for update 语句来实现锁机制。

代码示例

1. 依赖

首先,在 pom.xml 文件中添加以下依赖:

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.1.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

2. 数据源配置

application.yml 文件中配置数据源:

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false
    username: root
    password: root

3. 实体类

创建一个实体类 User

@Data
@TableName("user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;

    private String name;

    private Integer age;

    @Version
    private Integer version;
}

4. Mapper 接口

创建一个 UserMapper 接口:

public interface UserMapper extends BaseMapper<User> {
    @Select("select * from user where id = #{id} for update")
    User selectByIdForUpdate(Long id);
}

5. Service 实现

UserService 中实现更新操作:

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    @Transactional
    public void update() throws InterruptedException {
        User user = userMapper.selectByIdForUpdate(1L); // 加锁
        System.out.println("Thread " + Thread.currentThread().getName() + " read data: " + user);
        user.setAge(user.getAge() + 1);
        Thread.sleep(5000); // 模拟业务处理时间
        userMapper.updateById(user);
    }
}

6. 测试类

编写一个测试类:

@SpringBootTest
class ConcurrentUpdateTest {
    @Autowired
    private UserService userService;

    @Test
    void testConcurrentUpdate() throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        CountDownLatch countDownLatch = new CountDownLatch(2);

        executorService.execute(() -> {
            try {
                userService.update();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            countDownLatch.countDown();
        });

        executorService.execute(() -> {
            try {
                userService.update();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            countDownLatch.countDown();
        });

        countDownLatch.await();
    }
}

在测试方法中,创建两个线程并发执行更新操作,通过 CountDownLatch 来保证两个线程都执行完毕后再结束测试。

总结

使用悲观锁可以有效地解决并发更新同一行数据导致数据更新丢失的问题,保证数据的一致性。需要注意的是,悲观锁可能会影响程序的性能,因为它需要等待锁释放才能进行操作,所以在实际应用中需要根据具体的业务场景进行选择。

注意:

  • 在使用悲观锁时,需要确保 selectForUpdate 方法执行后,在更新数据之前没有其他线程对同一行数据进行修改,否则可能会出现数据不一致的问题。
  • 可以考虑使用乐观锁来提高并发性能,乐观锁在更新数据时会进行版本号校验,如果版本号相同则更新成功,否则更新失败。
  • 本文提供的代码示例仅供参考,实际应用中需要根据具体的需求进行调整。
SpringBoot + MyBatis-Plus 并发更新同一行数据导致更新丢失的解决方案:悲观锁

原文地址: https://www.cveoy.top/t/topic/nCPS 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录