1. 插入测试及雪花算法

CRUD扩展

Insert插入

@Test
void testInsert(){
    User user = new User();
    user.setName("levnli").setAge(23).setEmail("levnliservice@126.com");
    // 自动生成id
    int result = userMapper.insert(user);
    // 受影响的行数
    System.out.println(result);
    // 发现 id 会自动回填
    System.out.println(user);
}

数据库插入的id的默认值为:全局唯一的

主键生成策略:雪花算法

  • id自增策略是:ID_WORKER

  • snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID,其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生4096个ID),最后还有一个符号位,永远是0。可以保证几乎全球唯一

2. 不同主键策略测试

/**
 * 数据库ID自增
 */
AUTO(0),
/**
 * 该类型为未设置主键类型
 */
NONE(1),
/**
 * 用户输入ID
 * 该类型可以通过自己注册自动填充插件进行填充
 */
INPUT(2),

/* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */
/**
 * 全局唯一ID (idWorker)
 */
ID_WORKER(3),
/**
 * 全局唯一ID (UUID)
 */
UUID(4),
/**
 * 字符串全局唯一ID (idWorker 的字符串表示)
 */
ID_WORKER_STR(5);
  • 如果想选用策略为:AUTO
    • 实体类字段上添加@TableId(type = IdType.AUTO)
    • 数据库字段一定要是自增!
  • 再次测试插入

3. 更新操作

@Test
void testUpdate(){
    User user = new User().setId(1336837066142076930L).setName("test");
    int i = userMapper.updateById(user);
    System.out.println(i);
}
  • 所有的sql都是自动拼接,动态配置的

自动装填

  • 创建时间、修改时间!这些操作都是自动化完成的,不希望手动更新
  • 所有的数据库表:gmt_create、gmt_modified,几乎所以的表都要配置上,而且需要自动填充

4. 自动填充处理

方式一:数据库级别(工作中不允许修改数据库)

  • 在表中新增字段create_time,update_time

    ALTER TABLE `mybatis_plus`.`user` 
    ADD COLUMN `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' AFTER `email`,
    ADD COLUMN `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' AFTER `create_time`;
    
  • 再次测试插入方法,先把实体类同步

    • private Date createTime;
      private Date updateTime;
      
  • 再次更新查看即可

方式二:代码级别

  • 删除数据库的默认值、更新操作!

    • 去掉时间配置
    • ALTER TABLE mybatis_plus.user
      MODIFY COLUMN create_time datetime(0) NULL COMMENT '创建时间' AFTER email,
      MODIFY COLUMN update_time datetime(0) NOT NULL COMMENT '更新时间' AFTER create_time;
  • 实体类的字段属性上需要增加注解

    • // 字段添加填充内容
      @TableField(fill = FieldFill.INSERT)
      private Date createTime;
      @TableField(fill = FieldFill.INSERT_UPDATE)
      private Date updateTime;
      
  • 编写处理器,来处理这个注解即可

    @Slf4j
    @Component
    public class MyMetaObjectHandler implements MetaObjectHandler {
    
        /**
         * 插入时的填充策略
         * @param metaObject
         */
        @Override
        public void insertFill(MetaObject metaObject) {
            log.info("start insert fill....");
            this.setFieldValByName("createTime",new Date(),metaObject);
            this.setFieldValByName("updateTime",new Date(),metaObject);
        }
    
        /**
         * 更新时的填充策略
         * @param metaObject
         */
        @Override
        public void updateFill(MetaObject metaObject) {
            log.info("start updateTime fill....");
            this.setFieldValByName("updateTime",new Date(),metaObject);
        }
    }
    
  • 测试插入、更新

    • 观察时间即可

5. 乐观锁处理详解

  • 在面试过程中,经常被问到乐观锁、悲观锁

  • 乐观锁

    • 十分乐观,不会认为出现问题
    • 无论干什么都不会上锁
    • 如果出现了问题,再次更新值测试
    • version、new version
  • 悲观锁

    • 十分悲观,总是认为都会出现问题
    • 无论干什么都会上锁!再去操作
  • 主要讲解乐观锁机制

  • 乐观锁实现方式:

    • 取出记录时,获取当前version
    • 更新时,带上这个version
    • 执行更新时, set version = newVersion where version = oldVersion
    • 如果version不对,就更新失败
    乐观锁:先查询,获得版本号version = 1
    -- A线程
    update user set name = "test" , version = version + 1
    where id = 2 and version = 1
    
    -- B线程
    update user set name = "test" , version = version + 1
    where id = 2 and version = 1
    结果B线程抢先完成,这时候version = 2,导致A线程修改失败
    

测试一下MP的乐观锁插件

  • 给数据库中添加version字段

    • ALTER TABLE mybatis_plus.user
      MODIFY COLUMN version int(10) NULL DEFAULT 1 COMMENT '乐观锁' AFTER update_time
      ADD COLUMN version int(10) NULL COMMENT '乐观锁' AFTER update_time;
  • 实体类添加对应的字段

    • /**
       * 乐观锁version注解
       */
      @Version
      private Integer version;
      
  • 注册组件

    @EnableTransactionManagement
    @MapperScan("com.example.mapper")
    @Configuration
    public class MyBatisPlusConfig {
    
        // 注册乐观锁插件
        @Bean
        public OptimisticLockerInterceptor optimisticLockerInterceptor() {
            return new OptimisticLockerInterceptor();
        }
    
    }
    
  • 测试一下

    @Test
    void testOptimisticLocker(){
        // 查询用户信息
        User user = userMapper.selectById(6L);
        // 修改用户信息
        user.setEmail("123@qq.com");
        // 执行更新
        userMapper.updateById(user);
    }
    
    @Test
    void testOptimisticLockerThread(){
        // 模拟乐观锁,多线程并发
        // 线程1
        User user = userMapper.selectById(6L);
        user.setEmail("111@qq.com");
    
        // 线程2,抢先提交
        User user2 = userMapper.selectById(6L);
        user2.setEmail("222@qq.com");
        userMapper.updateById(user2);
    
        userMapper.updateById(user);
    }
    

6. 查询操作

  • 批量查询和条件查询
/**
 * 测试批量查询
 */
@Test
void testSelectByIds(){
    List<User> users = userMapper.selectBatchIds(Arrays.asList(1,2,3));
    users.forEach(System.out::println);
}

/**
 * 条件查询之一使用 map
 */
@Test
void selectByMap(){
    HashMap<String, Object> map = new HashMap<>();
    map.put("name","levnli");
    List<User> users = userMapper.selectByMap(map);
    users.forEach(System.out::println);
}

7. 分页查询实现

  • 分页查询在网站使用十分之多!
    • 原始的limit进行分页
    • pageHelper第三方插件
    • MP其实也内置了分页插件

如何使用

  • 配置拦截器组件即可

    • // 分页插件
      @Bean
      public PaginationInterceptor paginationInterceptor() {
          return new PaginationInterceptor();
      }
      
  • 直接使用Page对象即可

    • @Test
      void testPage(){
          // 参数:当前页、页面大小
          Page<User> page = new Page<>(2,5);
          userMapper.selectPage(page,null);
      
          // 打印查询结果
          page.getRecords().forEach(System.out::println);
          // 当前表总共条数
          System.out.println(page.getTotal());
      }
      

8. 删除操作

  • 根据ID删除记录

    /**
     * 通过ID删除
     */
    @Test
    void testDeleteById(){
        userMapper.deleteById(1336837066142076931L);
    }
    
  • 根据ID批量删除

    /**
     * 通过ID批量删除
     */
    @Test
    void testDeleteBatchIds(){
        userMapper.deleteBatchIds(Arrays.asList(1336837066142076931L));
    }
    
  • 根据条件删除

    /**
     * 通过条件删除
     */
    @Test
    void testDeleteMap(){
        HashMap<String, Object> map = new HashMap<>();
        map.put("name","test2");
        userMapper.deleteByMap(map);
    }
    

9. 逻辑删除

  • 物理删除:从数据库中直接移除
  • 逻辑删除:在数据库中没有被移除,而是通过一个变量来让他失效!
    • deleted = 0 -> deleted = 1
  • 管理员可以查看被删除的记录!防止数据的丢失,类似于回收站

测试一下:

  • 在数据报中增加一个deleted字段

    ALTER TABLE `mybatis_plus`.`user` 
    ADD COLUMN `deleted` int(1) NULL DEFAULT 0 COMMENT '删除标志位' AFTER `version`;
    
  • 在实体类上添加字段

    /**
     * 逻辑删除
     */
    @TableLogic
    private Integer deleted;
    
  • 添加插件

    // 逻辑删除插件
    @Bean
    public ISqlInjector sqlInjector() {
        return new LogicSqlInjector();
    }
    
  • 配置逻辑删除

    mybatis-plus:
      global-config:
        db-config:
          logic-delete-value: 1
          logic-not-delete-value: 0
    

10. 性能分析插件

开发中,会遇到慢sql

MP也提供性能分析插件,如果超过这个时间就是停止运行

  • 导入插件

    // SQL执行效率插件
    @Bean
    @Profile("dev")
    public PerformanceInterceptor performanceInterceptor(){
        PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
        // sql最大执行时间,超过不执行
        performanceInterceptor.setMaxTime(100);
        // 输出sql格式化
        performanceInterceptor.setFormat(true);
        return performanceInterceptor;
    }
    
  • 修改application.yml

    spring:
      profiles:
        active: dev
    
  • 测试使用

    • 执行任何一个方法