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


    乐观锁

    概念

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

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

    • 假如有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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

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

    @Configuration
    public class MpConfig {
        @Bean
        public MybatisPlusInterceptor mpInterceptor() {
            //1.定义Mp拦截器
            MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
            //2.添加乐观锁拦截器
            mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
            return mpInterceptor;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    步骤4:执行更新操作

    @SpringBootTest
    class Mybatisplus03DqlApplicationTests {
    
        @Autowired
        private UserDao userDao;
    	
        @Test
        void testUpdate(){
           User user = new User();
            user.setId(3L);
            user.setName("Jock666");
            userDao.updateById(user);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

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

    添加version数据

    @SpringBootTest
    class 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述

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

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

    @SpringBootTest
    class Mybatisplus03DqlApplicationTests {
    
        @Autowired
        private UserDao userDao;
    	
        @Test
        void testUpdate(){
            //1.先通过要修改的数据id将当前数据查询出来
            User user = userDao.selectById(3L);
            //2.将要修改的属性逐一设置进去
            user.setName("Jock888");
            userDao.updateById(user);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述

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

    @SpringBootTest
    class 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?条件还成立吗?
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    运行程序,分析结果:

    在这里插入图片描述

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

    参考官方文档来实现:

    https://mp.baomidou.com/guide/interceptor-optimistic-locker.html#optimisticlockerinnerinterceptor
    在这里插入图片描述

    代码生成器

    代码生成器原理分析

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

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

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

    在这里插入图片描述

    • ① 可以根据数据库表的表名来填充
    • ② 可以根据用户的配置来生成ID生成策略
    • ③到⑨可以根据数据库表字段名称来填充

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

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

    • 模板: MyBatisPlus提供,可以自己提供,但是麻烦,不建议
    • 数据库相关配置:读取数据库获取表和字段信息
    • 开发者自定义配置:手工配置,比如ID生成策略

    代码生成器实现

    步骤1:创建一个Maven项目

    代码2:导入对应的jar包

    
    <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.5.1version>
        parent>
        <groupId>com.itheimagroupId>
        <artifactId>mybatisplus_04_generatorartifactId>
        <version>0.0.1-SNAPSHOTversion>
        <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>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
                <scope>runtimescope>
            dependency>
    
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
    
            
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <version>1.18.12version>
            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>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84

    步骤3:编写引导类

    @SpringBootApplication
    public class Mybatisplus04GeneratorApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(Mybatisplus04GeneratorApplication.class, args);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    步骤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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

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

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

    步骤5:运行程序

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

    在这里插入图片描述

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

    MP中Service的CRUD

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

    public interface UserService{
    	
    }
    
    @Service
    public class UserServiceImpl implements UserService{
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

    public interface UserService{
    	public List<User> findAll();
    }
    
    @Service
    public class UserServiceImpl implements UserService{
        @Autowired
        private UserDao userDao;
        
    	public List<User> findAll(){
            return userDao.selectList(null);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

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

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

    public interface UserService extends IService<User>{
    	
    }
    
    @Service
    public class UserServiceImpl extends ServiceImpl<UserDao, User> implements UserService{
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

    编写测试类进行测试:

    @SpringBootTest
    class Mybatisplus04GeneratorApplicationTests {
    
        private IUserService userService;
    
        @Test
        void testFindAll() {
            List<User> list = userService.list();
            System.out.println(list);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

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

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

  • 相关阅读:
    计算机网络-数据链路层(无线局域网(802.11局域网,MAC帧头格式,无线局域网的分类,VLAN基本概念与基本原理))
    2023-简单点-IOU计算
    Day45 力扣动态规划 : 1143.最长公共子序列 |1035.不相交的线 | 53. 最大子序和
    实时监控电脑屏幕的软件丨同时查看12台电脑屏幕
    基于springboot小型车队管理系统毕业设计源码
    Visual Studio2019配置Lua及sol2框架
    设计模式:什么是设计模式?①
    SpringBoot 整合 Minio 实现 文件上传
    (十三)强缓存和协商缓存的区别
    JAVA:实现字符串WordLadder字梯算法(附完整源码)
  • 原文地址:https://blog.csdn.net/zyb18507175502/article/details/126053052