Springboot+mysql+redis实现简单的多⼈抢单秒杀详细代码项⽬源码
前话
同时使⽤@Transactional注解和 synchronized或者同时使⽤@Transactional注解和和分布式锁会造成线程安全问题,因为
@Transactional是⽤AOP实现的,当synchronized⾥⾯的⽅法运⾏完后,AOP的代码⾥⾯的事务提交可能还没运⾏,此时其他请求可以进去synchronized运⾏,结果就读到了还未提交的事务的数据,去掉@Transactional注解,使⽤⼿动开启事务,并提交,将所有代码都放在 synchronized⾥⾯,但是synchronized只能再单击起作⽤,多机只能使⽤分布式锁
⽅案⼀
使⽤mysql⾃带的for update
这样在第⼀次修改完成前 第⼆次⽆法查询 ⾃然就可以保证库存减少和订单数相同
⽅案⼆
抢单前将库存查询到redis中 使⽤redis的decrement来保证库存减少的原⼦性
然后⽤定时任务每隔⼀段时间将redis库存同步到mysql中
⽅案三
使⽤synchronized,只能在单机起作⽤
⽅案四
使⽤分布式锁
⽅案五
使⽤⼀条update代替select加update,依靠返回的影响⾏数,判断有⽆减库存成功,先查询再根据查询的值来update,由于查询是并发的,查询到快照或者说不是最新版本的值,再更新值就会出问题,但是并发update的话,数据库可以保证加上互斥锁,来保证操作原⼦,就算不是互斥锁,也会在快照并发修改后进⾏有效性检验
数据表结构
本次简单测试只⽤到两张表 六个字段
项⽬结构
引⼊依赖l
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="/POM/4.0.0"
xsi="/2001/XMLSchema-instance"
schemaLocation="/POM/4.0.0 /xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>springboot_grabbing_orders</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.1</version>
<relativePath/><!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<!-- mybatis plus 代码⽣成器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.28</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
</project>
配置⽂件
server:
port:9000
spring:
datasource:
driver-class-name: sql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/grabbing_orders?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior= convertToNull&useSSL=false&allowPublicKeyRetrieval=true
username: root
password:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
serialization:
write-dates-as-timestamps:false
redis:
port:6379
mybatis-plus:
mapper-locations: classpath*:mapper/**/*l
logging.level.itheima.mapper: debug #配置显⽰执⾏的sql语句
mybatis-plus构造器代码
运⾏main 输⼊表名 ⾃动连接数据库 构造代码
GeneratorCodeConfig.java
package;
import MybatisPlusException;
import StringUtils;
import AutoGenerator;
import*;
import NamingStrategy;
import FreemarkerTemplateEngine;
import Scanner;
public class GeneratorCodeConfig {
public static String scanner(String tip){
Scanner scanner =new Scanner(System.in);
StringBuilder help =new StringBuilder();
help.append("请输⼊"+ tip +": ");
System.out.String());
if(scanner.hasNext()){
String ipt = ();
if(StringUtils.isNotEmpty(ipt)){
scanner.close();;
return ipt;
}
}
scanner.close();
throw new MybatisPlusException("请输⼊正确的"+ tip);
}
public static void main(String[] args){
// 代码⽣成器
AutoGenerator mpg =new AutoGenerator();
// 全局配置
GlobalConfig gc =new GlobalConfig();
// 获取系统参数当前⽬录
String projectPaht = Property("user.dir");
gc.setOutputDir(projectPaht+"/src/main/java");
gc.setAuthor("astupidcoder");
gc.setOpen(false);
// 实体属性 Swagger2 注解
// 实体属性 Swagger2 注解
gc.setSwagger2(false);
mpg.setGlobalConfig(gc);
//数据源配置
DataSourceConfig dsc =new DataSourceConfig();
dsc.setUrl("jdbc:mysql://127.0.0.1:3306/grabbing_orders?serverTimezone=Asia/Shanghai&useUnico
de=true&characterEncoding=utf-8&zeroDateTime Behavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true");
dsc.setDriverName("sql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("");springboot aop
mpg.setDataSource(dsc);
// pc.setModuleName(scanner("模块名"));
// 包配置
PackageConfig pc =new PackageConfig();
pc
.setParent("com.itheima")
.setEntity("model")
.setMapper("mapper")
.setService("service")
.setServiceImpl("service.impl");
mpg.setPackageInfo(pc);
// ⾃定义配置
// InjectionConfig cfg = new InjectionConfig() {
// @Override
// public void initMap() {
// // to do nothing
// }
// };
/
/ 如果模板引擎是 freemarker
// String templatePath = "/l.ftl";
// 如果模板引擎是 velocity
// String templatePath = "/l.vm";
// ⾃定义输出配置
// List<FileOutConfig> focList = new ArrayList<>();
// ⾃定义配置会被优先输出
// focList.add(new FileOutConfig(templatePath) {
// @Override
// public String outputFile(TableInfo tableInfo) {
// // ⾃定义输出⽂件名,如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发⽣变化!!
// return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()
// + "/" + EntityName() + "Mapper" + StringPool.DOT_XML;
// }
// });
/*
cfg.setFileCreate(new IFileCreate() {
@Override
public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {
// 判断⾃定义⽂件夹是否需要创建
checkDir("调⽤默认⽅法创建的⽬录");
return false;
}
});
*/
// cfg.setFileOutConfigList(focList);
// mpg.setCfg(cfg);
// 配置模板
TemplateConfig templateConfig =new TemplateConfig();
// 配置⾃定义输出模板
//指定⾃定义模板路径,注意不要带上.ftl/.vm, 会根据使⽤的模板引擎⾃动识别
// templateConfig.setEntity("templates/entity2.java");
// templateConfig.setService();
/
/ templateConfig.setController();
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论