Spring Boot中的并发控制与事务处理

在开发中,我们经常会遇到多个用户同时对同一行数据进行修改的场景。这时候就需要考虑如何保证数据的一致性和完整性。本文将介绍如何在Spring Boot中实现并发控制,并保证同一个事务对同一行数据的同时修改。

并发控制的概念

并发控制是指在多个事务同时对同一数据进行修改时,如何保证数据的一致性和完整性。常见的并发控制策略包括乐观锁和悲观锁。

  • 乐观锁:每个事务在修改数据时都假设其他事务不会同时修改相同的数据。当提交事务时,系统会检查数据是否被其他事务修改过,如果没有则提交成功,否则回滚该事务。
  • 悲观锁:每个事务在修改数据时都假设其他事务会同时修改相同的数据。因此,在读取数据时就会对其加锁,在修改数据时再进行解锁。

Spring Boot提供了对乐观锁的支持,可以很方便地实现对同一行数据的同时修改。

实现乐观锁并发控制

步骤一:配置数据库

首先,我们需要在数据库中创建一个示例表,用于演示并发控制。这里我们创建一个名为user的表,包含idname两个字段。

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `version` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

步骤二:定义实体类

在Spring Boot中,我们使用JPA来操作数据库。首先,需要定义一个实体类User,对应数据库中的user表。

@Entity
@Table(name = "user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @Version
    private Long version;

    // 省略getter和setter方法
}

在实体类中,我们使用@Version注解标记了version字段。这个字段用来记录当前数据的版本号,每次更新数据时,版本号会自动加一。

步骤三:编写业务逻辑

接下来,我们编写业务逻辑,实现对数据库中数据的同时修改。

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    @Transactional
    public void updateUser(Long userId, String newName) {
        User user = userRepository.findById(userId).orElseThrow(() -> new RuntimeException("User not found"));
        user.setName(newName);
        userRepository.save(user);
    }
}

在上面的代码中,我们编写了一个updateUser方法,用于更新数据库中指定用户的姓名。在方法上添加了@Transactional注解,表示该方法是一个事务。在方法中,我们首先根据用户ID查询出对应的用户信息,然后修改其姓名,并调用userRepository.save(user)保存到数据库。

步骤四:测试并发修改

为了模拟并发修改的情况,我们可以使用多线程来同时调用updateUser方法。

@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/updateUser")
    public void updateUser() throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            try {
                userService.updateUser(1L, "User1");
            } catch (RuntimeException e) {
                e.printStackTrace();
            }
        });

        Thread thread2 = new Thread(() -> {
            try {
                userService.updateUser(1L, "User2");
            } catch (RuntimeException e) {
                e.printStackTrace();
            }
        });

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();
    }
}

在上面的代码中,我们通过创建两个线程thread1thread2,同时调用userService.updateUser方法来并发修改同一行数据。