写点什么

[MyBatisPlus] 乐观锁、代码生成器

作者:fake smile by
  • 2022 年 9 月 14 日
    黑龙江
  • 本文字数:6242 字

    阅读完需:约 20 分钟

乐观锁

概念

在讲解乐观锁之前,我们还是先来分析下问题:


业务并发现象带来的问题:==秒杀==


  • 假如有 100 个商品或者票在出售,为了能保证每个商品或者票只能被一个人购买,如何保证不会出现超买或者重复卖

  • 对于这一类问题,其实有很多的解决方案可以使用

  • 第一个最先想到的就是锁,锁在一台服务器中是可以解决的,但是如果在多台服务器下锁就没有办法控制,比如 12306 有两台服务器在进行卖票,在两台服务器上都添加锁的话,那也有可能会导致在同一时刻有两个线程在进行卖票,还是会出现并发问题

  • 我们接下来介绍的这种方式是针对于小型企业的解决方案,因为数据库本身的性能就是个瓶颈,如果对其并发量超过 2000 以上的就需要考虑其他的解决方案了。


==简单来说,乐观锁主要解决的问题是当要更新一条记录的时候,希望这条记录没有被别人更新。==

实现思路

乐观锁的实现方式:


  • 数据库表中添加 version 列,比如默认值给 1

  • 第一个线程要修改数据之前,取出记录时,获取当前数据库中的 version=1

  • 第二个线程要修改数据之前,取出记录时,获取当前数据库中的 version=1

  • 第一个线程执行更新时,set version = newVersion where version = oldVersion

  • newVersion = version+1 [2]

  • oldVersion = version [1]

  • 第二个线程执行更新时,set version = newVersion where version = oldVersion

  • newVersion = version+1 [2]

  • oldVersion = version [1]

  • 假如这两个线程都来更新数据,第一个和第二个线程都可能先执行

  • 假如第一个线程先执行更新,会把 version 改为 2,

  • 第二个线程再更新的时候,set version = 2 where version = 1,此时数据库表的数据 version 已经为 2,所以第二个线程会修改失败

  • 假如第二个线程先执行更新,会把 version 改为 2,

  • 第一个线程再更新的时候,set version = 2 where version = 1,此时数据库表的数据 version 已经为 2,所以第一个线程会修改失败

  • 不管谁先执行都会确保只能有一个线程更新数据,这就是 MP 提供的乐观锁的实现原理分析。


上面所说的步骤具体该如何实现呢?

实现步骤

分析完步骤后,具体的实现步骤如下:

步骤 1:数据库表添加列

列名可以任意,比如使用version,给列设置默认值为1


步骤 2:在模型类中添加对应的属性

根据添加的字段列名,在模型类中添加对应的属性值


@Data//@TableName("tbl_user") 可以不写是因为配置了全局配置public class User {    @TableId(type = IdType.ASSIGN_UUID)    private String id;    private String name;    @TableField(value="pwd",select=false)    private String password;    private Integer age;    private String tel;    @TableField(exist=false)    private Integer online;    private Integer deleted;    @Version    private Integer version;}
复制代码

步骤 3:添加乐观锁的拦截器

@Configurationpublic class MpConfig {    @Bean    public MybatisPlusInterceptor mpInterceptor() {        //1.定义Mp拦截器        MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();        //2.添加乐观锁拦截器        mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());        return mpInterceptor;    }}
复制代码

步骤 4:执行更新操作

@SpringBootTestclass Mybatisplus03DqlApplicationTests {
@Autowired private UserDao userDao; @Test void testUpdate(){ User user = new User(); user.setId(3L); user.setName("Jock666"); userDao.updateById(user); }}
复制代码



你会发现,这次修改并没有更新 version 字段,原因是没有携带 version 数据。


添加 version 数据


@SpringBootTestclass Mybatisplus03DqlApplicationTests {
@Autowired private UserDao userDao; @Test void testUpdate(){ User user = new User(); user.setId(3L); user.setName("Jock666"); user.setVersion(1); userDao.updateById(user); }}
复制代码



你会发现,我们传递的是 1,MP 会将 1 进行加 1,然后,更新回到数据库表中。


所以要想实现乐观锁,首先第一步应该是拿到表中的 version,然后拿 version 当条件在将 version 加 1 更新回到数据库表中,所以我们在查询的时候,需要对其进行查询


@SpringBootTestclass Mybatisplus03DqlApplicationTests {
@Autowired private UserDao userDao; @Test void testUpdate(){ //1.先通过要修改的数据id将当前数据查询出来 User user = userDao.selectById(3L); //2.将要修改的属性逐一设置进去 user.setName("Jock888"); userDao.updateById(user); }}
复制代码



大概分析完乐观锁的实现步骤以后,我们来模拟一种加锁的情况,看看能不能实现多个人修改同一个数据的时候,只能有一个人修改成功。


@SpringBootTestclass Mybatisplus03DqlApplicationTests {
@Autowired private UserDao userDao; @Test void testUpdate(){ //1.先通过要修改的数据id将当前数据查询出来 User user = userDao.selectById(3L); //version=3 User user2 = userDao.selectById(3L); //version=3 user2.setName("Jock aaa"); userDao.updateById(user2); //version=>4 user.setName("Jock bbb"); userDao.updateById(user); //verion=3?条件还成立吗? }}
复制代码


运行程序,分析结果:



乐观锁就已经实现完成了,如果对于上面的这些步骤记不住咋办呢?


参考官方文档来实现:


https://mp.baomidou.com/guide/interceptor-optimistic-locker.html#optimisticlockerinnerinterceptor


代码生成器

代码生成器原理分析


观察我们之前写的代码,会发现其中也会有很多重复内容,比如:



那我们就想,如果我想做一个 Book 模块的开发,是不是只需要将红色部分的内容全部更换成Book即可,如:



所以我们会发现,做任何模块的开发,对于这段代码,基本上都是对红色部分的调整,所以我们把去掉红色内容的东西称之为==模板==,红色部分称之为==参数==,以后只需要传入不同的参数,就可以根据模板创建出不同模块的 dao 代码。


除了 Dao 可以抽取模块,其实我们常见的类都可以进行抽取,只要他们有公共部分即可。再来看下模型类的模板:



  • ① 可以根据数据库表的表名来填充

  • ② 可以根据用户的配置来生成 ID 生成策略

  • ③到⑨可以根据数据库表字段名称来填充


所以只要我们知道是对哪张表进行代码生成,这些内容我们都可以进行填充。


分析完后,我们会发现,要想完成代码自动生成,我们需要有以下内容:


  • 模板: MyBatisPlus 提供,可以自己提供,但是麻烦,不建议

  • 数据库相关配置:读取数据库获取表和字段信息

  • 开发者自定义配置:手工配置,比如 ID 生成策略

代码生成器实现

步骤 1:创建一个 Maven 项目

代码 2:导入对应的 jar 包

<?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 https://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.5.1</version>    </parent>    <groupId>com.itheima</groupId>    <artifactId>mybatisplus_04_generator</artifactId>    <version>0.0.1-SNAPSHOT</version>    <properties>        <java.version>1.8</java.version>    </properties>    <dependencies>        <!--spring webmvc-->        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>
<!--mybatisplus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.1</version> </dependency>
<!--druid--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency>
<!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
<!--test--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
<!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> </dependency>
<!--代码生成器--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.4.1</version> </dependency>
<!--velocity模板引擎--> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.3</version> </dependency>
</dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
</project>
复制代码

步骤 3:编写引导类

@SpringBootApplicationpublic class Mybatisplus04GeneratorApplication {
public static void main(String[] args) { SpringApplication.run(Mybatisplus04GeneratorApplication.class, args); }
}
复制代码

步骤 4:创建代码生成类

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/mybatisplus_db?serverTimezone=UTC"); dataSource.setUsername("root"); dataSource.setPassword("root"); autoGenerator.setDataSource(dataSource);
//设置全局配置 GlobalConfig globalConfig = new GlobalConfig(); globalConfig.setOutputDir(System.getProperty("user.dir")+"/mybatisplus_04_generator/src/main/java"); //设置代码生成位置 globalConfig.setOpen(false); //设置生成完毕后是否打开生成代码所在的目录 globalConfig.setAuthor("黑马程序员"); //设置作者 globalConfig.setFileOverride(true); //设置是否覆盖原始生成的文件 globalConfig.setMapperName("%sDao"); //设置数据层接口名,%s为占位符,指代模块名称 globalConfig.setIdType(IdType.ASSIGN_ID); //设置Id生成策略 autoGenerator.setGlobalConfig(globalConfig);
//设置包名相关配置 PackageConfig packageInfo = new PackageConfig(); packageInfo.setParent("com.aaa"); //设置生成的包名,与代码所在位置不冲突,二者叠加组成完整路径 packageInfo.setEntity("domain"); //设置实体类包名 packageInfo.setMapper("dao"); //设置数据层包名 autoGenerator.setPackageInfo(packageInfo);
//策略设置 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); //2.执行生成操作 autoGenerator.execute(); }}
复制代码


对于代码生成器中的代码内容,我们可以直接从官方文档中获取代码进行修改,


https://mp.baomidou.com/guide/generator.html

步骤 5:运行程序

运行成功后,会在当前项目中生成很多代码,代码包含controller,servicemapperentity



至此代码生成器就已经完成工作,我们能快速根据数据库表来创建对应的类,简化我们的代码开发。

MP 中 Service 的 CRUD

回顾我们之前业务层代码的编写,编写接口和对应的实现类:


public interface UserService{  }
@Servicepublic class UserServiceImpl implements UserService{
}
复制代码


接口和实现类有了以后,需要在接口和实现类中声明方法


public interface UserService{  public List<User> findAll();}
@Servicepublic class UserServiceImpl implements UserService{ @Autowired private UserDao userDao; public List<User> findAll(){ return userDao.selectList(null); }}
复制代码


MP 看到上面的代码以后就说这些方法也是比较固定和通用的,那我来帮你抽取下,所以 MP 提供了一个 Service 接口和实现类,分别是:IServiceServiceImpl,后者是对前者的一个具体实现。


以后我们自己写的 Service 就可以进行如下修改:


public interface UserService extends IService<User>{  }
@Servicepublic class UserServiceImpl extends ServiceImpl<UserDao, User> implements UserService{
}
复制代码


修改以后的好处是,MP 已经帮我们把业务层的一些基础的增删改查都已经实现了,可以直接进行使用。


编写测试类进行测试:


@SpringBootTestclass Mybatisplus04GeneratorApplicationTests {
private IUserService userService;
@Test void testFindAll() { List<User> list = userService.list(); System.out.println(list); }
}
复制代码


注意:mybatisplus_04_generator 项目中对于 MyBatis 的环境是没有进行配置,如果想要运行,需要提取将配置文件中的内容进行完善后在运行。


思考:在 MP 封装的 Service 层都有哪些方法可以用?


查看官方文档:https://mp.baomidou.com/guide/crud-interface.html,这些提供的方法大家可以参考官方文档进行学习使用,方法的名称可能有些变化,但是方法对应的参数和返回值基本类似。

发布于: 刚刚阅读数: 5
用户头像

fake smile by

关注

还未添加个人签名 2022.07.31 加入

还未添加个人简介

评论

发布
暂无评论
[MyBatisPlus]乐观锁、代码生成器_Java_fake smile by_InfoQ写作社区