MyBatis-plus拓展之字段类型处理器、自动填充和乐观锁等(完结)

MyBatis-plus拓展

逻辑删除

逻辑删除就是增加一个字段表示这个数据的状态,通过状态来显示数据或隐藏数据,而不是真正的删除。

MyBatis-plus使用@TableLogic注解来标注逻辑删除字段:

java 复制代码
public class User extends Model<User> {
    @TableId
    private Long id;
    private String name;
    private Integer age;
    private String email;
    // 配置当前字段为逻辑删除字段
    // 默认值是1,删除状态的值是0
    @TableLogic(value = "1",delval = "0")
    private Integer status;
}

此时如果调用Mapper的删除方法,实际对应的sql语句是更新操作。将逻辑删除字段的值更新为0,而不是真正的删除。

而且此时Mapper的查询方法,不会查出这条数据。生成的sql语句会自动拼接where条件:status = 1

逻辑删除也可以在配置文件中进行全局配置:

yaml 复制代码
mybatis-plus:
  global-config:
    banner: false
    db-config:
      id-type: assign_id
      # 逻辑删除字段为status
      logic-delete-field: status
      # 删除状态的值
      logic-delete-value: 0
      # 未删除状态的值
      logic-not-delete-value: 1

使用全局配置后就不用使用@TableLogic注解了。

通用枚举

假如要表示性别:只有男和女两个值,我们就可以使用枚举来描述。

数据库表中使用gender (int 类型)表示性别,0表示女性,1表示男性。

使用@EnumValue来标注将哪个变量的值插入到数据库。

  1. 先创建枚举类

    java 复制代码
    public enum GenderEnum {
        MAN(1,"男"),WOMAN(0,"女");
        
        @EnumValue // 表示将这个变量的值插入到数据库
        private Integer gender;
        private String genderName;
    
        GenderEnum(Integer gender, String genderName) {
            this.gender = gender;
            this.genderName = genderName;
        }
    }
  2. 给Pojo类添加枚举属性

    java 复制代码
    public class User extends Model<User> {
        @TableId
        private Long id;
        private String name;
        private Integer age;
        private String email;
        // 配置当前字段为逻辑删除字段
        // 默认值是1,删除状态的值是0
        @TableLogic(value = "1",delval = "0")
        private Integer status;
        private GenderEnum gender;
    }
    1. 调用正常的插入方法即可实现。

字段类型处理器

某些场景下,实体类中使用map集合作为属性接收前端传来的数据,但是把这些输出存到数据库时,使用json格式的字符串存储。那怎么把map类型转换成字符串类型呢?这里就需要使用字段类型处理器。

需要@TableName注解和@TableField注解配合使用。

实体类代码如下:

java 复制代码
// autoResultMap = true 表示查询时,自动将contact字段json格式的字符串转换为Map类型
@TableName(autoResultMap = true)
public class User extends Model<User> {
    @TableId
    private Long id;
    private String name;
    private Integer age;
    private String email;
    // 配置当前字段为逻辑删除字段
    // 默认值是1,删除状态的值是0
    @TableLogic(value = "1",delval = "0")
    private Integer status;
    private GenderEnum gender;
    // 指定类型处理器,在新增或更新时,自动将Map类型转换成json格式的字符串
    // 这个处理器依赖于Fastjson,所以要在pom文件中引入Fastjson依赖
    @TableField(typeHandler = FastjsonTypeHandler.class)
    private Map<String, String> contact; // 联系方式
}

自动填充

在实际应用中,有一些属性,其实不需要我们每次都手动填充,可以设置为自动填充,比如创建时间、更新时间等可以设置为自动填充。

注意时区的设置:

  1. mysql数据库设置时区:set global time_zone = '+8:00'

查看时区对不对?执行select now() 看时间能不能对上。

  1. 项目中的时区设置:在数据库连接Url中设置。
yaml 复制代码
url: jdbc:mysql://localhost:3306/spring6?useSSL=false&serverTimezone=UTC&characterEncoding=utf8&useUnicode=true

把serverTimezone=UTC改为serverTimezone=Asia/Shanghai。

  1. 使用@TableField注解设置填充时机
java 复制代码
// autoResultMap = true 表示查询时,自动将contact字段json格式的字符串转换为Map类型
@TableName(autoResultMap = true)
public class User extends Model<User> {
    @TableId
    private Long id;
    private String name;
    private Integer age;
    private String email;
    // 配置当前字段为逻辑删除字段
    // 默认值是1,删除状态的值是0
    @TableLogic(value = "1",delval = "0")
    private Integer status;
    private GenderEnum gender;
    // 指定类型处理器,在新增或更新时,自动将Map类型转换成json格式的字符串
    // 这个处理器依赖于Fastjson,所以要在pom文件中引入Fastjson依赖
    @TableField(typeHandler = FastjsonTypeHandler.class)
    private Map<String, String> contact; // 联系方式
    // 指定插入时填充
    @TableField(fill = FieldFill.INSERT)
    private Data creatTime;
    // 指定插入或更新时填充
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Data modifyTime;
}
  1. 编写自定义处理器设置填充策略

    java 复制代码
    //自定义Handler,设置填充策略,这里需要实现MetaObjectHandler接口
    @Component
    public class MyMetaHandler implements MetaObjectHandler {
        // 插入时的填充策略
        @Override
        public void insertFill(MetaObject metaObject) {
            setFieldValByName("creatTime",new Date(),metaObject);
            setFieldValByName("modifyTime",new Date(),metaObject);
        }
    
        // 更新时的填充策略
        @Override
        public void updateFill(MetaObject metaObject) {
            setFieldValByName("modifyTime",new Date(),metaObject);
        }
    }

此时在新增或更新时,无需手动设置创建时间更新时间,系统会自动填充时间到数据库表中。

防止全表更新与删除插件

配置拦截规则 :插件默认拦截没有指定条件的 updatedelete 语句。

当触发拦截时,会抛出MyBatisPlusException异常。

通过在MybatisPlusConfig 这个配置类中加入对应的拦截器来阻止全表更新与删除:

java 复制代码
@Configuration
@MapperScan("com.ali.mapper")
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 防止全表更新与删除插件
        interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加
        // 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType
        return interceptor;
    }
}

MyBatisX快速开发插件

MyBatisX是一款idea提供的插件,目的是为了简化MyBatis和MyBatis-plus框架。

  1. MyBatisX先安装插件:

    File -- > Settings -->Plugins -- >搜索 MyBatisX,然后安装

  2. MyBatisX插件的快速定位:

    这个插件把Mapper接口的方法和映射文件的sql进行了一一对应。可以从Mapper接口方法快速跳到对应的映射文件对应的sql。

同样,也可以在映射文件点击小鸟快速跳转到对应的Mapper接口

  1. MyBatisX插件的逆向工程:

    通过数据库表,快速生成以下项目文件:

    • 实体类

    • Mapper接口

    • Mapper映射文件

    • Service接口

    • Service实现类

      1. 首先使用idea连接数据库:

      2. 选择对应的数据库表,然后右键选择MyBatisX-generate

  2. 最后一步设置

这样就生成了对应的项目文件。注意这里的Mapper接口文件要手动加上@Mapper注解。

乐观锁

并发请求就是在同一时刻有多个请求去请求同一个服务器资源。如果是获取信息,不会出现问题,但是如果做修改操作,就会出现并发问题。

比如:三个人去买同样的商品,商品剩余一件。购买时一般先查询库存再购买后数量减一,并发请求就是同一时刻,三个人都查到了商品剩余1个,然后同时进行购买。这样只能一个人买到,另外两人肯定买不到,此时就发生了超卖行为。这就是经典的并发问题。

常见的数据库锁类型有两种,悲观锁和乐观锁:

悲观锁:查询时就锁定数据,在请求完成之前不会释放锁。完成后才释放锁。释放锁以后,其他请求才可以对数据进行读写。

这样虽解决了并发问题,但是效率较低。实际开发中很少使用悲观锁。

乐观锁:通过表字段完成设计。乐观锁的核心思想是:在读取的时候不加锁,其他请求仍可以读取这个数据,在修改的时候,判断一个数据是否有被修改过,如果修改过,那本次请求的修改操作失效。

具体设计如下:

增加一个字段version。购买商品时先查询,此时能获取到version的值。

在执行购买操作时,更新库存数量前再查询一次version的值,如果两次的version值一样,表示可以进行更新库存操作,更新时进行 version = version +1,表示我执行了一次。这样就完成了乐观锁的操作。

如果两次的version 值不一致,说明有人对库存数据进行了更新,此时不能直接进行购买。需要重新进行先查询后购买的操作。

MyBatis-plus中乐观锁实现步骤如下:

  1. 在实体类中使用@Version注解指定版本字段

    java 复制代码
    // autoResultMap = true 表示查询时,自动将contact字段json格式的字符串转换为Map类型
    @TableName(autoResultMap = true)
    public class User extends Model<User> {
        @TableId
        private Long id;
        private String name;
        private Integer age;
        private String email;
        // 配置当前字段为逻辑删除字段
        // 默认值是1,删除状态的值是0
        @TableLogic(value = "1",delval = "0")
        private Integer status;
        private GenderEnum gender;
        // 指定类型处理器,在新增或更新时,自动将Map类型转换成json格式的字符串
        // 这个处理器依赖于Fastjson,所以要在pom文件中引入Fastjson依赖
        @TableField(typeHandler = FastjsonTypeHandler.class)
        private Map<String, String> contact; // 联系方式
        // 指定插入时填充
        @TableField(fill = FieldFill.INSERT)
        private Data creatTime;
        // 指定插入或更新时填充
        @TableField(fill = FieldFill.INSERT_UPDATE)
        private Data modifyTime;
        @Version // 表示将此字段作为版本信息
        private Integer version;// 版本
    }
    1. 在MybatisPlusConfig配置类中加入乐观锁拦截器

      java 复制代码
      @Configuration
      @MapperScan("com.ali.mapper")
      public class MybatisPlusConfig {
          @Bean
          public MybatisPlusInterceptor mybatisPlusInterceptor() {
              MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
              // 乐观锁拦截器
              interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
              // 防止全表更新与删除插件
              interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
              interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加
              // 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType
              return interceptor;
          }
      }
  2. 测试代码:

    java 复制代码
    // 第一次查询:执行成功   version是1
    User user1 = userService.getById(1L);
    // 第二次查询:执行成功   version是1
    User user5 = userService.getById(1L);
    
    user1.setAge(12);
    // 第一次更新:执行成功,因为再次获取到version还是1,和上次保持一致。
    // 更新后 version +1 此时version是2
    userService.updateById(user1);
    
    user5.setAge(22);
    // 第二次更新:执行失败,因为再次获取到version的值是2,和上次获取的值不一样,不执行更新操作。
    userService.updateById(user5);

代码生成器

代码生成器和逆向功能的区别在于,代码生成器可以生成更多的结构,更多的内容,允许配置更多的内容。

具体步骤如下:

  1. 引入相关依赖

    xml 复制代码
    <!--代码生成器-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-generator</artifactId>
                <version>3.5.3</version>
            </dependency>
            <!--freemarker 模板依赖-->
            <dependency>
                <groupId>org.freemarker</groupId>
                <artifactId>freemarker</artifactId>
                <version>2.3.31</version>
            </dependency>
  2. 编写生成类(可以在mybatis-plus官网直接copy):

    java 复制代码
    public class CodeGeneratorTest {
        public static void main(String[] args) {
            // 使用 FastAutoGenerator 快速配置代码生成器
            FastAutoGenerator.create("jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8", "root", "password")
                    .globalConfig(builder -> {
                        builder.author("Your Name") // 设置作者
                                .outputDir("src/main/java"); // 输出目录
                    })
                    .packageConfig(builder -> {
                        builder.parent("com.example") // 设置父包名
                                .entity("model") // 设置实体类包名
                                .mapper("dao") // 设置 Mapper 接口包名
                                .service("service") // 设置 Service 接口包名
                                .serviceImpl("service.impl") // 设置 Service 实现类包名
                                .xml("mappers"); // 设置 Mapper XML 文件包名
                    })
                    .strategyConfig(builder -> {
                        builder.addInclude("table1", "table2") // 设置需要生成的表名
                                .entityBuilder()
                                .enableLombok() // 启用 Lombok
                                .enableTableFieldAnnotation() // 启用字段注解
                                .controllerBuilder()
                                .enableRestStyle(); // 启用 REST 风格
                    })
                    .templateEngine(new FreemarkerTemplateEngine()) // 使用 Freemarker 模板引擎
                    .execute(); // 执行生成
        }
    }

    执行sql打印分析

    通过sql分析来获取sql语句的执行时间。

    具体步骤如下:

    1. 由于该功能依赖于p6spy组件,p6spy是一个强大的工具,它为MyBatis-Plus用户提供了便捷的SQL分析与打印功能。通过合理配置,你可以在开发和测试阶段有效地监控和优化SQL语句。然而,由于性能损耗,建议在生产环境中谨慎使用。所以先引入依赖:

      xml 复制代码
      <dependency>
          <groupId>p6spy</groupId>
          <artifactId>p6spy</artifactId>
          <version>3.9.1</version>
      </dependency>
      1. 在application.yml中修改配置

        yaml 复制代码
        spring:
          datasource:
            username: root
            password: root
            url: jdbc:p6spy:mysql://localhost:3306/spring6?useSSL=false&serverTimezone=UTC&characterEncoding=utf8&useUnicode=true
            driver-class-name: com.p6spy.engine.spy.P6SpyDriver
  3. 在resource下,创建spy.properties配置文件

    properties 复制代码
    # 模块列表,根据版本选择合适的配置
    modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
    
    # 自定义日志格式
    logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
    
    # 日志输出到控制台
    appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
    
    # 取消JDBC驱动注册
    deregisterdrivers=true
    
    # 使用前缀
    useprefix=true
    
    # 排除的日志类别
    excludecategories=info,debug,result,commit,resultset
    
    # 日期格式
    dateformat=yyyy-MM-dd HH:mm:ss
    
    # 实际驱动列表
    # driverlist=org.h2.Driver
    
    # 开启慢SQL记录
    outagedetection=true
    
    # 慢SQL记录标准(单位:秒)
    outagedetectioninterval=2
    
    # 过滤 flw_ 开头的表 SQL 打印
    filter=true
    exclude=flw_*

    多数据源

    1. 先引入依赖

      xml 复制代码
      <dependency>
          <groupId>com.baomidou</groupId>
          <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
          <version>3.1.0</version>
      </dependency>

dynamic-datasource 是一个开源的 Spring Boot 多数据源启动器,提供了丰富的功能,包括数据源分组、敏感信息加密、独立初始化表结构等

  1. 配置数据源

    yaml 复制代码
    spring:
      datasource:
        dynamic:
        # 默认数据源名为 master,可通过 spring.datasource.dynamic.primary 修改。
          primary: master
          strict: false
          datasource:
            master:
              url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
              username: root
              password: 123456
              driver-class-name: com.mysql.jdbc.Driver
            slave_1:
              url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
              username: root
              password: 123456
              driver-class-name: com.mysql.jdbc.Driver
            slave_2:
              url: ENC(xxxxx)
              username: ENC(xxxxx)
              password: ENC(xxxxx)
              driver-class-name: com.mysql.jdbc.Driver
  2. 使用 @DS 切换数据源

    java 复制代码
    @Service
    @DS("master")
    public class UserServiceImpl implements UserService {
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @Override
        @DS("slave_1")
        public List selectByCondition() {
            return jdbcTemplate.queryForList("select * from user where age >10");
        }
    }
相关推荐
摇滚侠2 小时前
从 Tomcat 服务最大连接数角度讲一讲高峰期高考查分网站打不开,服务器的资源是有限的,同一时间大量用户连接服务器,会耗尽服务器的资源,服务器会拒绝新的连接
java·服务器·tomcat
中国lanwp2 小时前
Maven Gradle SBT Mill Ivy Grape Leiningen Buildr构建工具
java·maven
肥猪猪爸2 小时前
数据库 2PC 极简流程图
java·数据库·分布式·mysql·分布式事务·2pc
二月夜2 小时前
Maven 避坑指南:高频配置错误总结 & 解决方案
java·maven
一只空白格2 小时前
ThreadLocal的作用和底层原理
java·开发语言·jvm
沐苏瑶2 小时前
Java数据结构-LinkedList与链表
java·数据结构·链表
今天你TLE了吗2 小时前
JVM学习笔记:第九章——StringTable字符串常量池
java·jvm·笔记·后端·学习
心前阳光2 小时前
Mirror网络库插件使用4
java·linux·网络·unity·c#·游戏引擎
Rsun045512 小时前
定时任务如何保证任务的可靠性和幂等性?
java