04-MyBatisPlus之DML编程控制

四、DML编程控制

4.1、id生成策略(insert)

问题导入

  • 主键生成的策略有哪几种方式?
  • 不同的表应用不同的id生成策略
    • 日志:自增(1,2,3,4,……)
    • 购物订单:特殊规则(FQ23948AK3843)
    • 外卖单:关联地区日期等信息(10 04 20200314 34 91)
    • 关系表:可省略id
    • ...

同时针对每个公司,随着服务化演进,单个服务越来越多,数据库分的越来越细,有的时候一个业务需要分成好几个库,这时候自增主键或者序列之类的主键id生成方式已经不再满足需求,分布式系统中需要的是一个全局唯一的id生成规则。

具体的算法和代码可以参考雪花算法(SnowFlake),有需要的可以评论区评论或者直接私聊鄙人

4.1.1、@TalbeId注解

  • 名称

    • @TableId
  • 类型

    • 属性注解
  • 位置

    • 模型类中用于表示主键的属性,定义在上方
  • 作用

    • 设置当前类中主键属性的生成策略
  • 相关属性

    • type:设置主键属性的生成策略,值参照IdType枚举值
  • 测试步骤

    • 1、添加无参和有参的构造方法

      • package com.coolman.model;
        
        import com.baomidou.mybatisplus.annotation.IdType;
        import com.baomidou.mybatisplus.annotation.TableField;
        import com.baomidou.mybatisplus.annotation.TableId;
        import com.baomidou.mybatisplus.annotation.TableName;
        import lombok.AllArgsConstructor;
        import lombok.Data;
        import lombok.NoArgsConstructor;
        
        @Data
        @AllArgsConstructor
        @NoArgsConstructor
        @TableName(value = "tbl_user")
        public class User {
        
            //使用雪花算法
            @TableId(type = IdType.ASSIGN_ID)
            private Long id;
            
            private String name;
            private String gender;
        
        //    private String pwd;
        //    @TableField(value = "pwd")    // 指定该字段在数据库中的列名
        //    @TableField(select = false) // 不参与查询
            private String password;
        
            private Integer age;
            private String tel;
        
            //test new member
        //    @TableField(exist = false)  // 该字段在数据库中不存在
        //    private Integer online;
        }
        
        
    • 2、插入数据测试

4.1.2、全局策略配置

  • 除了使用上述的@TableId注解之外,还可以在applicaion.yml中进行全局的配置

  • 测试步骤

    • 1、id-type 让所有表主键生成策略相同

    • 2、table-prefix在每个实体类的前面添加相同的前缀

    • mybatis-plus:
        global-config:
          db-config:
            id-type: assign_id
            table-prefix: tbl_
      

4.1.2.1、id生成策略全局配置

4.1.2.2、表名前缀全局配置

  • 实体类去掉@TableName和@TableId注解

    • package com.coolman.model;
      
      import com.baomidou.mybatisplus.annotation.IdType;
      import com.baomidou.mybatisplus.annotation.TableField;
      import com.baomidou.mybatisplus.annotation.TableId;
      import com.baomidou.mybatisplus.annotation.TableName;
      import lombok.AllArgsConstructor;
      import lombok.Data;
      import lombok.NoArgsConstructor;
      
      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      //@TableName(value = "tbl_user")
      public class User {
      
          //使用雪花算法
      //    @TableId(type = IdType.ASSIGN_ID)
          private Long id;
      
          private String name;
          private String gender;
      
      //    private String pwd;
      //    @TableField(value = "pwd")    // 指定该字段在数据库中的列名
      //    @TableField(select = false) // 不参与查询
          private String password;
      
          private Integer age;
          private String tel;
      
          //test new member
      //    @TableField(exist = false)  // 该字段在数据库中不存在
      //    private Integer online;
      }
      
      
  • 再次进行新增测试

4.2、多记录操作(批量Delete/Select)

问题导入

  • MyBatisPlus是否支持批量操作?

4.2.1、按照主键删除多条记录

  • 方法

    • deleteBatchIds()
  • 代码

    • package com.coolman.test;
      
      import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
      import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
      import com.coolman.mapper.UserMapper;
      import com.coolman.model.User;
      import lombok.extern.slf4j.Slf4j;
      import org.junit.jupiter.api.Test;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.test.context.SpringBootTest;
      
      import java.util.ArrayList;
      import java.util.List;
      import java.util.Map;
      
      @Slf4j
      @SpringBootTest
      public class MyBatisPlusTest04 {
      
          @Autowired(required = false)
          private UserMapper userMapper;
      
          // 批量删除
          @Test
          public void selectByInterval() {
      
              ArrayList<Long> list = new ArrayList<>();
      
              list.add(1541476421560475649L);
              list.add(1542481093725270017L);
              list.add(1542481194023673857L);
              list.add(1542481281839804417L);
              list.add(1542481341910663169L);
      
              userMapper.deleteBatchIds(list);
      
          }
      }
      
    • 生成的SQL代码如下所示

      • DELETE FROM tbl_user WHERE id IN ( ? , ? , ? , ? , ? )

4.2.2、根据主键查询多条记录

  • 方法

    • selectBathchIds()
  • 代码

    • // 根据主键批量查询
          @Test
          public void selectBatchByIds() {
              ArrayList<Long> list = new ArrayList<>();
              
              list.add(1L);
              list.add(2L);
              list.add(3L);
              list.add(4L);
              list.add(5L);
              list.add(6L);
      
              List<User> users = userMapper.selectBatchIds(list);
      
              for (User user : users) {
                  log.info(user + "");
              }
          }
      
    • 生成的SQL代码如下所示

    • SELECT id,name,gender,password,age,tel FROM tbl_user WHERE id IN ( ? , ? , ? , ? , ? , ? )

4.3、逻辑删除(Delete/Update)

在实际生产环境中,如果想删除一条数据,是不会真的从数据库删除该条数据的

  • 删除操作业务问题:业务数据从数据库中丢失
  • 逻辑删除:为数据设置是否可用状态的字段,删除时状态设置状态字段为不可用状态,数据保留在数据库中

4.3.1、逻辑删除案例

  • 演示步骤

    • 1、修改之前的User表结构,添加逻辑删除标记字段

      • -- 添加一列deleted字段,设置默认值为0(0为未删除,1为已删除)
        alter table tbl_user add column deleted int(1) default 0;
        
        desc tbl_user;
        
    • 2、实体类中添加对应字段,并设定当前字段为逻辑删除标记字段

      • @TableLogic注解包含以下两个属性

        • value:未删除时的值
        • delval:删除了的值
      • // logic delete
            @TableLogic
            private Integer deleted;
        
    • 3、在配置文件中配置逻辑删除字面值

    • 4、执行逻辑删除操作(逻辑删除的本质其实是修改操作。如果加了逻辑删除字段,查询数据时也会自动带上逻辑删除字段

      • // 逻辑删除
            @Test
            public void logicDeleteTest(){
                userMapper.deleteById(5);
            }
        
    • 生成的SQL语句如下所示

      • UPDATE tbl_user SET deleted=1 WHERE id=? AND deleted=0
      • 上面的deleted=0条件,在添加逻辑删除字段之后,每次执行查询或者修改的时候都会带上这个条件
  • 数据库的数据如下图所示

4.4、乐观锁(Update)

乐观锁主张的思想

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

4.4.1、悲观锁(Pessimistic Lock)的概念

  • 当要对数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发。这种借助数据库锁机制,在修改数据之前先锁定,再修改的方式被称之为悲观锁。
  • 之所以叫做悲观锁,是因为这是一种对数据的修改持有悲观态度的并发控制方式。总是假设最坏的情况,每次读取数据的时候都默认其他线程会更改数据,因此线程想要访问数据时,都需要阻塞挂起。

4.4.2、乐观锁(Optimistic Locking)的概念

  • 乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,乐观锁适用于读多写少的场景,这样可以提高程序的吞吐量。
  • 乐观锁采取了更加宽松的加锁机制。也是为了避免数据库幻读、业务处理时间过长等原因引起数据处理错误的一种机制,但乐观锁不会刻意使用数据库本身的锁机制,而是依据数据本身来保证数据的正确性。
  • 乐观锁的实现
    • 1、CAS实现
      • Java 中java.util.concurrent.atomic包下面的原子变量使用了乐观锁的一种 CAS 实现方式。
    • 2、版本号控制
      • 一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version 值会 +1。当线程 A 要更新数据时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值与当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。

4.4.3、乐观锁案例

  • 操作步骤

    • 1、数据库表中添加锁标记字段

      • alter table tbl_user add column version int default 0;
        
        desc tbl_user;
        
    • 2、实体类中添加对应字段,并设定当前字段为版本控制手段

      • @Version注解

      • // lock
            @Version
            private Integer version;
        
    • 3、配置乐观锁拦截器实现锁机制对应的动态SQL语句拼装

    • 4、使用乐观锁机制在修改前必须先获取到对应数据的version才可以正常进行

      • // 乐观锁测试
            @Test
            public void optimisticLockTest(){
        
                // 1. 查询当前要修改的记录
                User user = userMapper.selectById(5);
        
                // 2. 修改数据
                user.setAge(37);
        
                userMapper.updateById(user);
            }
        
      • 执行结果如下所示

        • 很显然,更新操作执行一次version就会+1
    • 5、模拟多条记录同时更新

      • // 乐观锁测试2
            @Test
            public void optimisticLockTest2(){
        
                // 1. 查询当前要修改的记录
                User user1 = userMapper.selectById(1);
                User user2 = userMapper.selectById(1);
        
                // 2. 修改数据
                user1.setAge(44);
                user2.setAge(99);
        
                userMapper.updateById(user1);
                userMapper.updateById(user2);
            }
        
  • 结果演示

posted @ 2022-06-30 21:35  OnlyOnYourself-Lzw  阅读(146)  评论(0)    收藏  举报