【SpringBoot并发事务处理】

需求

假设现在有1000个项目,要从excel文件导入系统,即有一个1000行的文件导入系统,因为导入的时候要对每个项目做校验,导致导入很慢,现在需要进行优化。
规则:

  1. 如果有项目名称重复,则提示项目重复。
  2. 如果以前数据库没有此名称项目则新增,如果有则修改。

涉及技术

EasyExcel,SpringBoot线程池,并发异步处理

思路

这个需求耗时有两点,1是进行校验,2是进行添加修改操作
针对第一点 :我们使用CompletableFuture进行异步校验;
针对第二点 :为了保证事务,我们使用两个CountDownLatchAtomicBoolean保证事务,mainLatch 和rollbackLatch用来阻塞事务提交,使用isSuccess来代表事务是否提交

细节点

  1. 同一个类中异步注解失效,需要重新获取bean对象调用方法才可以

实例

ProjectServiceImpl

package com.qp.projectJUC.service.impl;

import cn.hutool.core.lang.UUID;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.google.common.collect.Lists;
import com.qp.projectJUC.listener.ProjectImportListener;
import com.qp.projectJUC.mapper.ProjectInfoMapper;
import com.qp.projectJUC.pojo.ProjectImportDto;
import com.qp.projectJUC.pojo.ProjectInfo;
import com.qp.projectJUC.service.ProjectInfoService;
import com.qp.projectJUC.util.SpringContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;


@Slf4j
@Service
public class ProjectInfoServiceImpl implements ProjectInfoService {

    @Autowired
    @Qualifier("projectImportExecutor")
    private ThreadPoolTaskExecutor projectImportExecutor;

    @Autowired
    private ProjectInfoMapper projectInfoMapper;

    @SuppressWarnings("rawtypes")
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<String> importProject(MultipartFile file) {
        String seq = UUID.fastUUID().toString();
        String msg = "ok";
        List<ProjectImportDto> parsedData = new CopyOnWriteArrayList<>();
        ProjectImportListener listener = new ProjectImportListener(parsedData);
        try {
            EasyExcel.read(file.getInputStream(), ProjectImportDto.class, listener).sheet().headRowNumber(1).doRead();
            CompletableFuture[] futures = Lists.partition(parsedData, 30).stream().map(batch -> CompletableFuture.runAsync(() -> listener.new PreCheck(batch, seq).preCheck(), projectImportExecutor).whenComplete((res, ex) -> {
                if (ex != null) {
                    listener.getSuccess().set(false);
                    log.error("seq=>{},import target value fail, e:", seq, ex);
                }
            })).toArray(CompletableFuture[]::new);
            CompletableFuture.allOf(futures).get();

            //校验失败返回信息
            if (!listener.getSuccess().get()) {
                return listener.getMsg();
            }
            //校验重名
            List<String> sameName = parsedData.stream().map(ProjectImportDto::getProjectName).collect(Collectors.toMap(e -> e, e -> 1, Integer::sum)).entrySet().stream().filter(entry -> entry.getValue() > 1).map(Map.Entry::getKey).collect(Collectors.toList());
            if (CollectionUtils.isNotEmpty(sameName)) {
                List<String> s = new ArrayList<>();
                sameName.forEach(name -> s.add("【" + name + "】" + "项目名重名"));
                return s;
            }

            //进行入库
            List<ProjectImportDto> createList = new CopyOnWriteArrayList<>();
            List<ProjectImportDto> updateList = new CopyOnWriteArrayList<>();
            for (ProjectImportDto parsedDatum : parsedData) {
                ProjectInfo po = parsedDatum.getProjectInfo();
                //查询状态
                List<ProjectInfo> projectInfoList = projectInfoMapper.selectByName(po.getProjectName());
                if (CollectionUtils.isEmpty(projectInfoList)) {
                    createList.add(parsedDatum);
                } else {
                    updateList.add(parsedDatum);
                }
            }
            List<List<ProjectImportDto>> createTasks = Lists.partition(createList, 30);
            List<List<ProjectImportDto>> updateTasks = Lists.partition(updateList, 30);
            AtomicBoolean isSuccess = new AtomicBoolean(true);
            CountDownLatch mainLatch = new CountDownLatch(createTasks.size() + updateTasks.size());
            CountDownLatch rollbackLatch = new CountDownLatch(1);

            ProjectInfoServiceImpl service = SpringContextUtil.getBean(ProjectInfoServiceImpl.class);

            for (List<ProjectImportDto> part : createTasks) {
                service.createProject(part, mainLatch, rollbackLatch, isSuccess);
            }

            for (List<ProjectImportDto> part : updateTasks) {
                //更新
                service.updateProject(part, mainLatch, rollbackLatch, isSuccess);
            }
            try {
                mainLatch.await();
            } finally {
                rollbackLatch.countDown();
            }
            if (!isSuccess.get()) {
                return Lists.newArrayList("项目导入错误");
            }
        } catch (IOException e) {
            log.error("seq=>{},import target value fail, e:", seq, e);
            throw new RuntimeException("file_error");
        } catch (ExcelDataConvertException e) {
            log.error("seq=>{},项目导入模板不正确", seq, e);
            return Lists.newArrayList("项目导入模板不正确");
        } catch (Exception e) {
            log.error("seq=>{},项目导入错误", seq, e);
            return Lists.newArrayList("项目导入错误");
        }
        return Lists.newArrayList(msg);
    }

    @Async("projectImportExecutor")
    @Transactional(rollbackFor = Exception.class)
    public void createProject(List<ProjectImportDto> datas, CountDownLatch mainLatch, CountDownLatch rollbackLatch, AtomicBoolean isSuccess) {
        try {
            for (ProjectImportDto data : datas) {
                if (!isSuccess.get()) {
                    throw new RuntimeException("file_error");
                }
                //创建项目方法
//                createImportProject(data.getProjectInfo());
            }
        } catch (Exception e) {
            isSuccess.set(false);
            log.error("import project fail, e:", e);
        } finally {
            mainLatch.countDown();
            try {
                rollbackLatch.await();
                if (!isSuccess.get()) {
                    //事务回滚
                    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                }
            } catch (InterruptedException e) {
                isSuccess.set(false);
                log.error("Create project code fail", e);
                //事务回滚
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            }
        }

    }

    @Async("projectImportExecutor")
    @Transactional(rollbackFor = Exception.class)
    public void updateProject(List<ProjectImportDto> datas, CountDownLatch mainLatch, CountDownLatch rollbackLatch, AtomicBoolean isSuccess) {
        try {
            for (ProjectImportDto data : datas) {
                if (!isSuccess.get()) {
                    throw new RuntimeException("file_error");
                }
                //更新项目方法
//                updateImportProject(data.getProjectInfo());
            }
        } catch (Exception e) {
            isSuccess.set(false);
            log.error("import project fail, e:", e);
        } finally {
            mainLatch.countDown();
            try {
                rollbackLatch.await();
                if (!isSuccess.get()) {
                    //事务回滚
                    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                }
            } catch (InterruptedException e) {
                isSuccess.set(false);
                log.error("update import project code fail", e);
                //事务回滚
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            }
        }
    }
}

listener

package com.qp.projectJUC.listener;


import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.qp.projectJUC.pojo.ProjectImportDto;
import com.qp.projectJUC.pojo.ProjectInfo;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

@Slf4j
@Data
public class ProjectImportListener implements ReadListener<ProjectImportDto> {

    private List<ProjectImportDto> results;

    private List<ProjectInfo> projectInfoList;
    /**
     * 返回信息
     */
    public List<String> msg;
    /**
     * 是否解析成功
     */
    public AtomicBoolean success;

    public ProjectImportListener(List<ProjectImportDto> results) {
        this.results = results;
    }


    @Override
    public void invoke(ProjectImportDto data, AnalysisContext context) {
        log.info("项目导入start");
        data.setRowIndex(context.readRowHolder().getRowIndex());
        results.add(data);
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        //do nothing
    }

    public class PreCheck {

        private String seq;

        private List<ProjectImportDto> data;

        public PreCheck(List<ProjectImportDto> data, String seq) {
            this.data = data;
            this.seq = seq;
        }

        public void preCheck() {
            log.info("start pre check seq:{}", seq);

            for (ProjectImportDto dto : data) {
                Integer rowIndex = dto.getRowIndex();
                int rowNum = rowIndex + 1;
                //模板校验
                if (rowIndex < 1) {
                    checkModule(dto, rowIndex);
                    continue;
                }
                if (!success.get()) {
                    return;
                }

                ProjectInfo projectInfo = new ProjectInfo();
                //进行校验,并且给ProjectInfo赋值
                if (StringUtils.isBlank(dto.getProjectName())) {
                    success.set(false);
                    msg.add("第" + rowNum + "行数据【项目名称】字段不正确");
                }else{
                    projectInfo.setProjectName(dto.getProjectName());
                }
                if (StringUtils.isBlank(dto.getLeaderName())) {
                    success.set(false);
                    msg.add("第" + rowNum + "行数据【项目主人】字段不正确");
                }else{
                    projectInfo.setLeaderName(dto.getLeaderName());
                }
                if (StringUtils.isBlank(dto.getRemark())) {
                    success.set(false);
                    msg.add("第" + rowNum + "行数据【项目描述】字段不正确");
                }else{
                    projectInfo.setRemark(dto.getRemark());
                }
                dto.setProjectInfo(projectInfo);
            }
        }

        public void checkModule(ProjectImportDto dto, Integer rowIndex) {
            if (!StringUtils.equals("* 序号", dto.getIndex())) {
                success.set(false);
            }

            if (!StringUtils.equals("* 项目名称", dto.getProjectName())) {
                success.set(false);
            }
            if (!StringUtils.equals("* 项目主人姓名", dto.getLeaderName())) {
                success.set(false);
            }
            if (!StringUtils.equals("* 项目描述", dto.getRemark())) {
                success.set(false);
            }
        }
    }
}



ProjectImportDto

package com.qp.projectJUC.pojo;


import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;

@Data
public class ProjectImportDto {

    @ExcelProperty(value = "序号", index = 0)
    private String index;
    @ExcelProperty(value = "项目名称", index = 1)
    private String projectName;
    @ExcelProperty(value = "项目主人名称", index = 2)
    private String leaderName;
    @ExcelProperty(value = "项目描述", index = 3)
    private String remark;

    private ProjectInfo projectInfo;
    private Integer rowIndex;
}

ProjectInfo

package com.qp.projectJUC.pojo;

import lombok.Data;

@Data
public class ProjectInfo {

    private String projectName;
    private String leaderName;
    private String remark;
}

SpringContextUtil

package com.qp.projectJUC.util;


import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringContextUtil implements ApplicationContextAware {

    private static volatile ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextUtil.applicationContext = applicationContext;
    }

    /**
     * 获取ApplicationContext
     * @return
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /**
     * 通过class获取Bean
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }

    /**
     * 通过name以及class获取Bean
     * @param name
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(String name, Class<T> clazz) {
        return applicationContext.getBean(name, clazz);
    }
}


线程池

package com.qp.projectJUC.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

@Configuration
public class ExecutorConfig {

    @Bean(name = "projectImportExecutor")
    public ThreadPoolTaskExecutor projectImportExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        int cpuNum = Runtime.getRuntime().availableProcessors();
        // 设置核心线程数
        executor.setCorePoolSize(20);
        // 设置最大线程数
        executor.setMaxPoolSize(30);
        // 设置队列容量
        executor.setQueueCapacity(1000);
        // 设置默认线程名称
        executor.setThreadNamePrefix("project-import-");
        // 设置拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任务结束后再关闭线程池. 当线程池要关闭的时候用  ExecutorService.shutdown(), ExecutorService.shutdownNow()
        // ExecutorService.shutdown(); 停止接收新任务,原来的任务继续执行
        // ExecutorService.shutdownNow();停止接收新任务,原来的任务停止执行
        executor.setWaitForTasksToCompleteOnShutdown(true);
        //设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保能够被关闭,而不是阻塞住
        executor.setAwaitTerminationSeconds(60);
        executor.setAllowCoreThreadTimeOut(true);
        return executor;
    }
}



maven依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.15</version>
        <relativePath/>
    </parent>

    <groupId>com.qp</groupId>
    <artifactId>study</artifactId>
    <version>1.0.0</version>

    <name>study</name>

    <properties>
        <java.version>8</java.version>
        <mysql.version>8.0.33</mysql.version>
        <fastjson.version>2.0.38</fastjson.version>
        <spring-cloud.version>2021.0.8</spring-cloud.version>
        <hutool.version>5.8.12</hutool.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!-- SpringBoot相关 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>


        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>

        <!--mysql 数据库相关-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.4.5</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>3.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>32.1.3-jre</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
        </dependency>
    </dependencies>

    <build>
        <finalName>study</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <encoding>UTF-8</encoding>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第1章 课程介绍 介绍该课程的内容、学习成果、实例,还有学习所需的前提知识。 1-1 导学-分布式事务实践 第2章 事务原则与实现 介绍了事务的四大原则,并通过实例介绍数据库实现事务的方法,以及使用JDBC实现事务的方法。 2-1 事务原则与实现:事务 2-2 事务原则与实现:SQL事务 2-3 事务原则与实现:JDBC事务(上) 2-4 事务原则与实现:JDBC事务(下) 第3章 使用Docker搭建环境 介绍了Docker的使用,通过Docker将课程环境搭建起来,方便那些不了解这些技术的同学之后的学习。 3-1 docker简介与mysql安装-1 3-2 docker简介与mysql安装-2 3-3 SpringBoot基础 第4章 Spring事务机制 介绍了Spring的事务机制、事物抽象、内部事务和外部事物,以及常用的几种事务管理的实现,包括DataSource、JPA、JMS、JTA都通过实例进行说明。还有XA以及两阶段提交,并通过实例演示了使用JTA,通过两阶段提交,实现多数据源的事务实现。... 4-1 Spring事务机制_基本接口 4-2 Spring事务机制_实现 4-3 Jpa事务实例 4-4 Jms事务原理 4-5 Jms-session事务实例 4-6 Jms-spring事务实例 4-7 外部事务与JTA 4-8 JTA单数据源事务实例 4-9 JTA多数据源事务实例 第5章 分布式系统 介绍了分布式系统的定义、实现原则和几种形式,详细介绍了微服务架构的分布式系统,并使用Spring Cloud框架演示了一个完整的微服务系统的实现过程。 5-1 CAP原则和BASE理论简介 5-2 分布式系统综述 5-3 SpringCloud微服务架构 5-4 实现registry 5-5 实现proxy 5-6 user服务 5-7 order服务 5-8 添加hystrix 5-9 使用feign 5-10 优化服务间调用 第6章 分布式事务实现,模式和技术 介绍分布式事务的定义、原则和实现原则,介绍使用Spring框架实现分布式事务的几种方式,包括使用JTA、Spring事务同步、链式事务等,并通过实战介绍其实现。除此以外还介绍了一些分布式事务相关的技术,如幂等性、全局一致性ID、分布式对象等。... 6-1 分布式事务介绍 6-2 spring分布式事务实现_使用JTA 6-3 spring分布式事务实现_不使用JTA 6-4 实例1-DB-DB 6-5 实例1-DB-DB.链式事务管理器 6-6 实例2-JPA-DB.链式事务管理器 6-7 实例3-JMS-DB.最大努力一次提交 6-8 分布式事务实现模式与技术 6-9 全局一致性ID和分布式对象_ 第7章 分布式事务实现:消息驱动模式 详细介绍3种分布式事务实现的模式中的消息驱动模式并通过完整实例演示了消息驱动模式下,实现微服务系统的分布式事务的完整过程。 7-1 分布式事务实现:消息驱动模式 7-2 消息驱动模式实例:设计 7-3 消息驱动模式实例:创建ticket服务 7-4 消息驱动模式实例:实现基本ticket功能 7-5 消息驱动模式实例:锁票1 7-6 消息驱动模式实例:锁票2 7-7 按消息流程实现业务 7-8 支付过程 7-9 票转移 7-10 错误处理:锁票失败 7-11 错误处理:扣费失败 7-12 并发时的错误处理 第8章 分布式事务实现:Event Sourcing模式 详细介绍了分布式事务实现的模式中的Event Sourcing模式,并通过完整实例演示了Event Sourcing模式下,实现微服务系统的分布式事务的完整过程。 8-1 事件溯源模式介绍 8-2 事件溯源模式与Axon框架-1 8-3 事件溯源模式与Axon框架-2 8-4 使用Axon框架的设计过程介绍 8-5 Axon框架-实例(上) 8-6 Axon框架-实例(下) 8-7 Saga模式和Axon Saga 8-8 聚合命令事件(上) 8-9 聚合命令事件(下) 8-10 实现saga 8-11 实现query 8-12 处理超时 8-13 并发测试 8-14 cloud-axon实例:分布式处理介绍 8-15 事件设计 8-16 事件与队列设计 8-17 实现User服务 8-18 实现Ticket服务 8-19 实现Order服务 8-20 实现读写分离 8-21 测试与并发 8-22 事件溯源模式与Axon框架总结 第9章 TCC模式和微服务架构的设计模式 本章介绍TCC模式,也对微服务系统的几种设计模式,以及这些模式下分布式事务的实现模式进行了介绍。 9-1 TCC模式介绍 9-2 微服务架构的设计模式 第10章 课程总
【资源说明】 1、基于SpringBoot的高并发选课系统源码+项目说明(毕设).zip 2、该资源包括项目的全部源码,下载可以直接使用! 3、本项目适合作为计算机、数学、电子信息等专业的课程设计、期末大作业和毕设项目,作为参考资料学习借鉴。 4、本资源作为“参考资料”如果需要实现其他功能,需要能看懂代码,并且热爱钻研,自行调试。 ## 项目简介 本项目主要解决在高校选课场景下,保证选课系统在大量读写压力下不宕机,以及选课时尽可能提高选课QPS,给学生一个良好的选课体验,完成上述功能同时保证选课安全 ## 技术选型 前端:Bootstrap、JQuery、Thymeleaf 后端SpringBoot、Shiro、JPA、Caffeine 中间件:Redis、RabbitMQ、Druid 数据库:MySQL ## 优化思路 ### 页面查看 解决思路:从Redis缓存中查看数据,减少数据库访问 从数据可见性角度来讲,分为对所有人可见的公有信息,和只有自己可见的私有信息 ### 登陆 1. 学生可能多次登陆系统,第一次登陆时将学生信息加载进Redis,减少后续登陆时对数据库的访问 2. 通过单例模式构建全局唯一类,根据sessionID保存学生学号,供后续使用 ### 选课 **_此功能为整个系统的重点优化之处,_** 主要分为两大步骤,选课安全验证和执行选课操作 #### **选课安全验证** 1. IP限流,每分钟可以访问三次,与学号绑定,通过Redis实现 ```java Integer sno = StudentIDUtils.getStudentIDFromMap(); Integer count = (Integer) redisService.get("ip-", String.valueOf(sno)); if (count == null) { //一分钟内可以访问三次 redisService.set("ip-", String.valueOf(sno), 1, 1, TimeUnit.MINUTES); }else if (count < 3){ redisService.incr("ip-"+sno, 1); }else { throw new GlobalException(CodeMsg.COUNT_OVER); } ``` 2. 判断是否存在该课程,从Redis中查看 3. 判断是否在规定选课时限范围内,从Redis中查看 4. 若满足上述步骤,根据课程号生成其md5值,暴露秒杀地址 5. 执行选课操作时验证秒杀地址是否正确 #### 执行选课操作 1. 通过本地标记判断是否有余量,若有,执行后续 2. 判断是否重选,通过查看Redis是否有对应缓存来实现 3. 判断上课时间是否冲突,构造冲突判断算法,遍历Redis中已选课程进行验证 4. 库存预减,当库存为0时,将该授课计划添加到本地缓存中,本地缓存通过Caffeine构建 ```java Long num = redisService.hdecr("forPlanCount", String.valueOf(pno), 1); // LOGGER.info("redis中读取pno={} 的授课计划余量为{}", pno, num); if(num < 0){ //没余量,写入本地缓存中 caffeineCache.put(pno, true); throw new GlobalException(CodeMsg.PlAN_OVER); } ``` 5. 选课请求压入MQ,异步执行,流量削峰,消费端消费选课信息,将结果写入数据库,结果写入数据库这一步采用事务机制,先插入结果,后减余量,减少事务期间锁持有时间,优化数据库读写性能 6. 返回执行成功标识,但结果需要到选课查询页面确认 #### 预加载 ```java @Service public class ChooseServiceImpl implements ChooseService, InitializingBean { @Override public void afterPropertiesSet() throws Exception { //预加载相关操作 } } ``` 通过上述代码所示,Spring
Spring Boot 中,事务处理非常简单,只需要添加注解即可。通常情况下,我们使用 @Transactional 注解来标记一个方法需要事务处理。 使用 @Transactional 注解时,需要注意以下几点: 1. 在类或方法上添加 @Transactional 注解,表示该类或方法需要事务处理。 2. 在方法上添加 @Transactional 注解时,默认情况下事务只在抛出 RuntimeException 及其子类异常时回滚。 3. 可以通过设置 rollbackFor 属性来指定需要回滚的异常类型。 4. 事务只在 public 方法上起作用。 5. 如果在一个类的方法中调用另一个方法,被调用的方法上的 @Transactional 注解不会起作用,需要在被调用的方法所在的类上添加 @Transactional 注解。 示例代码如下: ```java @Service public class UserService { @Autowired private UserDao userDao; @Transactional public void save(User user) { userDao.save(user); } @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void delete(Long id) throws Exception { User user = userDao.findById(id); if (user == null) { throw new Exception("用户不存在"); } userDao.delete(user); } } ``` 在上面的示例代码中,我们定义了两个方法:save 和 delete。在 save 方法上添加了 @Transactional 注解,表示该方法需要事务处理。在 delete 方法上,我们通过设置 propagation 属性来指定事务的传播行为,通过设置 rollbackFor 属性来指定需要回滚的异常类型。 需要注意的是,在使用事务处理时,一定要保证事务的粒度要尽可能小,以减少事务的锁定时间,提高并发性能。同时,对于需要回滚的异常类型,一定要慎重选择,避免将不需要回滚的异常也加入回滚列表中,导致事务回滚过于频繁,影响性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值