一、乐观锁(update)🍭
业务并发现象带来的问题: 秒杀。
我们应该都遇过买东西限量秒杀吧,这个时候这么多人一起抢,我们应该怎么去实现秒杀程序呢?
下面我们会讲解2000访问量的秒杀实现,如果是更多人的话就应该使用其他更好的方法了。
1、添加字段和实体类属性🍉
添加version字段,默认值为1
实体类属性也添加应该version属性:
package com.example.domain; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; @Data /*@TableName("tbl_user")*/ public class User { @TableId(type= IdType.ASSIGN_ID) private Long id; private String name; @TableField(value = "pwd",select = false) private String password; private Integer age; private String tel; /*@TableLogic(value = "0",delval = "1")*/ private Integer deleted; @Version private Integer version; @TableField(exist = false) private Integer online; }
2、@Version原理🍉
当人人去进行秒杀时,成功抢到商品的用户,会更新用户的version值
update set_abc=1,version = version + 1 where version=1
如上面这个SQL一样。当一个仅剩的一个商品被抢走,这个version值就会变化,其他人就会显示抢不到商品。
添加乐观锁拦截器(和分页功能一样)🍓
使用这个和分页功能一样,需要添加拦截器:
package com.example.config; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MpConfig { @Bean public MybatisPlusInterceptor mpInterceptor(){ //1.定义Mp拦截器 MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor(); //2.添加具体的拦截器 mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor()); //3、添加乐观锁拦截器 mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return mpInterceptor; } }
3、测试代码🍓
我们去修改用户id=2的用户名:
@Test void testUpdate(){ User user=new User(); user.setId(2L); user.setName("编程"); userDao.updateById(user); }
运行发现,version值并没有发生改变:
这是因为我们没有去添加version值,便无法改变version值。
@Test void testUpdate(){ User user=new User(); user.setId(2L); user.setVersion(1); user.setName("编程"); userDao.updateById(user); }
添加用户的version发现,进行修改操作之后version值+1了:
但是这样手动添加version值很麻烦,我们可以先查询用户信息,然后去进行修改,也是可以的:
@Test void testUpdate(){ //1.先通过要修改的数据id将当前数据查询出来 User user = userDao.selectById(2L); //2.将要修改的属性逐一设置进去 user.setName("编程1号"); userDao.updateById(user); }
可以看到version经过用户修改又加了1。
4、秒杀测试🍓
我们前后修改两次用户名,看最终结果如何:
@Test void testUpdate(){ //1.先通过要修改的数据id将当前数据查询出来 User user = userDao.selectById(2L); //version=3 User user2 = userDao.selectById(2L); //version=3 user2.setName("编程 aaa"); userDao.updateById(user2); //version=>4 user.setName("编程 bbb"); userDao.updateById(user); //verion=3?条件还成立吗? }
可以看到只修改了一次,用户名修改为了 编程 aaa ,而后面的修改操作未进行:
这和我们上面说的情况是一样的,用户信息进行了修改,version值也会加一,因为你一开始查询到的version值为3,这个时候version已经变成了4,就无法进行修改成为 编程 bbb 了。
二、代码生成器🍭
1、模板🍉
既然是代码生成器,那肯定是有模板的,那我们来看看UserDao,观察看看,哪些是模板里的,哪些是需要更改的。
package com.example.dao; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.domain.User; import org.apache.ibatis.annotations.Mapper; @Mapper public interface UserDao extends BaseMapper { }
看下面,对于不同的Dao类只需要将下面红框框里的东西替换就行,其他的就是模板。换句话说我们只要将下面红框框里面的东西替换,又是一个新的Dao类。
不仅仅是Dao类实体类也可以提取出模板。
2、代码生成器🍉
模板:MyBatisPlus提供
数据库相关配置:读取数据库获取信息
开发者自定义配置:手工配置
Ⅰ、配置🍓
我们新建一个SpringBoot项目:
pom.xml:其中有所需要的代码生成器和velocity模板引擎以及其他需要的配置文件
"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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0modelVersion> <parent> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-parentartifactId> <version>2.7.14version> <relativePath/> parent> <groupId>com.examplegroupId> <artifactId>mybatis-plusartifactId> <version>0.0.1-SNAPSHOTversion> <name>mybatis-plusname> <description>mybatis-plusdescription> <properties> <java.version>1.8java.version> properties> <dependencies> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webartifactId> dependency> <dependency> <groupId>com.baomidougroupId> <artifactId>mybatis-plus-boot-starterartifactId> <version>3.4.1version> dependency> <dependency> <groupId>com.alibabagroupId> <artifactId>druidartifactId> <version>1.1.16version> dependency> <dependency> <groupId>com.mysqlgroupId> <artifactId>mysql-connector-jartifactId> <scope>runtimescope> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-testartifactId> <scope>testscope> dependency> <dependency> <groupId>org.projectlombokgroupId> <artifactId>lombokartifactId> <optional>trueoptional> dependency> <dependency> <groupId>com.baomidougroupId> <artifactId>mybatis-plus-generatorartifactId> <version>3.4.1version> dependency> <dependency> <groupId>org.apache.velocitygroupId> <artifactId>velocity-engine-coreartifactId> <version>2.3version> dependency> dependencies> <build> <plugins> <plugin> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-maven-pluginartifactId> plugin> plugins> build> project>
Generator类:添加数据库配置
package com.example; import com.baomidou.mybatisplus.generator.AutoGenerator; import com.baomidou.mybatisplus.generator.config.DataSourceConfig; public class Generator { public static void main(String[] args) { AutoGenerator autoGenerator=new AutoGenerator(); DataSourceConfig dataSource=new DataSourceConfig(); dataSource.setDriverName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/ku2022?characterEncoding=utf8"); dataSource.setUsername("root"); dataSource.setPassword("123456"); autoGenerator.setDataSource(dataSource); autoGenerator.execute(); } }
运行之后会给我打开一个文件夹:
可以发现多了一个com文件夹,我们打开里面的controller文件夹,可以发现它给这个库里面每一个表都生成了一个.java文件,这个就是给我们生成的代码:
但是这个东西生成的是不正确的,所以我们将这个文件夹删除掉。
Ⅱ、正确生成代码 🍓
我们应该添加其他配置,让他生成在正确的位置:
package com.example; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.generator.AutoGenerator; import com.baomidou.mybatisplus.generator.config.DataSourceConfig; import com.baomidou.mybatisplus.generator.config.GlobalConfig; public class CodeGenerator { public static void main(String[] args) { //1.获取代码生成器的对象 AutoGenerator autoGenerator = new AutoGenerator(); //设置数据库相关配置 DataSourceConfig dataSource = new DataSourceConfig(); dataSource.setDriverName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/ku2022?serverTimezone=UTC"); dataSource.setUsername("root"); dataSource.setPassword("123456"); autoGenerator.setDataSource(dataSource); //设置全局配置 GlobalConfig globalConfig = new GlobalConfig(); //输出的位置 globalConfig.setOutputDir(System.getProperty("user.dir")+"/src/main/java"); //设置代码生成位置 globalConfig.setOpen(false); //设置生成完毕后是否打开生成代码所在的目录 globalConfig.setAuthor("热爱编程的林兮"); //设置作者 globalConfig.setFileOverride(true); //设置是否覆盖原始生成的文件 globalConfig.setMapperName("%sDao"); //设置数据层接口名,%s为占位符,指代模块名称 globalConfig.setIdType(IdType.ASSIGN_ID); //设置Id生成策略 autoGenerator.setGlobalConfig(globalConfig); //2.执行生成操作 autoGenerator.execute(); } }
我们看到上面,点开GlobalConfig源码:
默认生成代码在D盘。
Ⅲ、生成的代码1🍓
我们运行代码:
可以看到在Java目录下的con文件夹生成了一个baomidou文件夹。
Ⅳ、生成的代码2🍓
我们继续在CodeGenerator添加新配置:
//设置包名相关配置 PackageConfig packageInfo = new PackageConfig(); packageInfo.setParent("com.aaa"); //设置生成的包名,与代码所在位置不冲突,二者叠加组成完整路径 packageInfo.setEntity("domain"); //设置实体类包名 packageInfo.setMapper("dao"); //设置数据层包名 autoGenerator.setPackageInfo(packageInfo);
我们把之前的代码(baomidou)删除掉,重新运行代码。
可以发现重新生成了一个名字为aaa文件夹,不再是原来的名字了。还有实体类的包名也修改了,变成了domin,数据层包名也变成了dao。
Ⅴ、生成的代码3 🍓
//策略设置 StrategyConfig strategyConfig = new StrategyConfig(); strategyConfig.setInclude("tbl_user"); //设置当前参与生成的表名,参数为可变参数 strategyConfig.setTablePrefix("tbl_"); //设置数据库表的前缀名称,模块名 = 数据库表名 - 前缀名 例如: User = tbl_user - tbl_ strategyConfig.setRestControllerStyle(true); //设置是否启用Rest风格 strategyConfig.setVersionFieldName("version"); //设置乐观锁字段名 strategyConfig.setLogicDeleteFieldName("deleted"); //设置逻辑删除字段名 strategyConfig.setEntityLombokModel(true); //设置是否启用lombok autoGenerator.setStrategy(strategyConfig);
之前库里面有五个表,这次通过上面代码我只生成了tbl_user表的代码(只想生成我指定表的代码),而且表的实体类加了@Data注解(启用了lombok),设置了乐观锁字段名还有逻辑删除字段名,以及删除了数据表的tbl_前缀。