springboot 操作sql改变状态的时候,怎么防止并发操作带来的问题

在Spring Boot中,防止并发操作带来的问题可以通过以下几种方式:

  • 使用事务管理:Spring框架提供了事务管理功能,可以通过事务的隔离级别和传播行为来控制并发操作。例如,使用事务的隔离级别来防止脏读和不可重复读,使用事务的传播行为来控制并发操作的线程顺序。

  • 加锁操作:对于需要修改状态的操作,可以使用数据库的行级锁或表级锁来限制并发操作。例如,使用MySQL的悲观锁或乐观锁来实现并发控制。

  • 使用乐观锁:乐观锁是一种乐观的并发控制方式,它在操作数据时不会加锁,而是在更新数据时检查是否有人同时修改了数据。如果有人同时修改了数据,则通过版本号等机制来处理冲突。Spring框架提供了乐观锁的支持,可以通过在实体类上添加@Version注解来实现。

  • 使用分布式锁:如果应用程序部署在多个节点上,可以使用分布式锁来控制并发操作。分布式锁可以通过使用Redis、Zookeeper等中间件来实现,确保在多个节点上的操作是互斥的。

  • 限流操作:通过限制并发操作的数量来避免过多的并发请求,可以使用Spring的@Async注解和线程池来实现限流操作。

第一种举个例子:

当涉及到并发操作时,使用事务管理是一种常见的方法。在Spring框架中,事务管理是通过使用@Transactional注解来实现的。下面是一个使用事务管理来防止并发操作的详细示例:

假设我们有一个UserService类,它包含了一个更新用户状态的方法:
1.直接使用@Transactional注解

@Service  
public class UserService {  
      
    @Autowired  
    private UserRepository userRepository;  
      
     // 1.直接使用@Transactional注解
    @Transactional  
    public void updateUserStatus(Long userId, String newStatus) {  
        User user = userRepository.findById(userId).orElseThrow(() -> new UserNotFoundException("User not found"));  
        user.setStatus(newStatus);  
        userRepository.save(user);  
    } 
}

2.通过编程式事务管理来实现不抛出异常的情况下手动提交事物。这通常涉及使用PlatformTransactionManager和TransactionDefinition来手动控制事务的开始、提交和回滚。

private final PlatformTransactionManager transactionManager;

@Override
    public String taskResolve(YstbtqTaskResolveParam params) {
        // 编程式事务管理-手动控制事物
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setName("TaskResolveTransaction");
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        TransactionStatus status = transactionManager.getTransaction(def);
        try {
            // 生成32位的id
            String uuid = UUID.randomUUID().toString().replaceAll("-", "");
            OnlineUser userInfo = HttpUtil.getUserInfo();
            params.getTask().setPkId(uuid);
            params.getTask().setCreateTime(LocalDateTime.now());
            params.getTask().setCreateBy(userInfo.getId());
            // 生成疑似变化图斑空间表
            Result<String> layerName = arcgisDataService.generateYSBHTBSpatialTable();
            params.getTask().setLayerName(layerName.getResult());
            // 插入地类图斑任务信息表
            int retTask = ysbhtbTaskMapper.insert(params.getTask());
            if (retTask == 0) {
                // 插入失败,回滚事务
                transactionManager.rollback(status);
                return "插入主任务信息失败!";
            }
            AtomicBoolean isGridSuccess = new AtomicBoolean(true);
            for (YsbhtbSubTask o : params.getSubTasks()) {
                // 生成网格数据到数据库
                FishnetGenerateParam generateFishnetParams = new FishnetGenerateParam();
                generateFishnetParams.setCellHeight(params.getCellHeight());
                generateFishnetParams.setCellWidth(params.getCellWidth());
                generateFishnetParams.setGeometrySrid(geometrySrid);
                generateFishnetParams.setGeometryWkt(o.getGeom());
                Result<String> gridLayerName = arcgisDataService.generateFishnet(generateFishnetParams);
                // 判断网格是否正常生成
                if (gridLayerName.getCode() != 0) {
                    isGridSuccess.set(false);
                    break;
                }
                o.setGridLayerName(gridLayerName.getResult());
                o.setPkId(UUID.randomUUID().toString().replaceAll("-", ""));
                o.setFkTaskId(uuid);
                o.setCreateTime(LocalDateTime.now());
                o.setCreateBy(userInfo.getId());
                o.setStatus(0);
            }
            if (!isGridSuccess.get()) {
                transactionManager.rollback(status);
                return "格网尺寸不匹配";
            };
            // 插入地类图斑子任务信息表
            boolean subTaskBool = ysbhtbSubTaskMapper.insertBatch(params.getSubTasks());
            if (subTaskBool) {
                // 如果所有操作都成功,则提交事务
                transactionManager.commit(status);
                return "操作成功";
            } else {
                transactionManager.rollback(status);
                return "插入子任务信息失败!";
            }
        } catch (Exception e) {
            // 发生异常时回滚事务
            transactionManager.rollback(status);
            return "操作失败:" + e.getMessage();
        }
    }

在上面的示例中,我们使用了@Transactional注解来标识updateUserStatus方法。这意味着当该方法被调用时,它将在事务的上下文中执行。

在事务的上下文中,当多个线程同时调用该方法时,Spring将会使用数据库的事务隔离级别来防止脏读和不可重复读。具体来说,Spring将会根据配置的事务隔离级别(例如READ_COMMITTED、READ_UNCOMMITTED等)来确定一个线程在执行更新操作时其他线程能否看到未提交的事务数据。

另外,如果多个线程同时调用该方法,Spring将会使用数据库的锁机制来确保只有一个线程可以获得更新操作的锁,其他线程需要等待锁释放后再继续执行。这样可以避免多个线程同时修改同一行数据导致的数据不一致问题。

需要注意的是,使用事务管理需要配置好数据库连接池和事务管理器。在Spring Boot中,可以通过配置DataSource和PlatformTransactionManager来配置事务管理器。例如,在application.properties文件中添加以下配置:

spring.datasource.driver-class-name=com.mysql.jdbc.Driver  
spring.datasource.url=jdbc:mysql://localhost:3306/mydb  
spring.datasource.username=root  
spring.datasource.password=password  
  
spring.tx.default-transaction-timeout=30s  
spring.tx.default-isolation-level=READ_COMMITTED
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐