java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > MyBatis-plus字段类型处理器

MyBatis-plus字段类型处理器、自动填充和乐观锁详解

作者:NE_STOP

文章主要内容涵盖了MyBatis-plus的逻辑删除、枚举使用、字段类型处理器、自动填充、防止全表更新与删除插件、MyBatisX插件的使用、乐观锁实现、代码生成器等技术点的介绍和配置方法,感兴趣的朋友跟随小编一起看看吧

MyBatis-plus拓展

逻辑删除

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

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

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

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

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来标注将哪个变量的值插入到数据库。

先创建枚举类

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;
    }
}

给Pojo类添加枚举属性

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格式的字符串存储。那怎么把map类型转换成字符串类型呢?这里就需要使用字段类型处理器。

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

实体类代码如下:

// 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中设置。
url: jdbc:mysql://localhost:3306/spring6?useSSL=false&serverTimezone=UTC&characterEncoding=utf8&useUnicode=true

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

  1. 使用@TableField注解设置填充时机
// 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;
}

编写自定义处理器设置填充策略

//自定义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 这个配置类中加入对应的拦截器来阻止全表更新与删除:

@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框架。

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

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

  1. 最后一步设置

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

乐观锁

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

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

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

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

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

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

具体设计如下:

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

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

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

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

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

// 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;// 版本
}

在MybatisPlusConfig配置类中加入乐观锁拦截器

@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;
    }
}

测试代码:

// 第一次查询:执行成功   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);

代码生成器

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

具体步骤如下:

引入相关依赖

<!--代码生成器-->
        <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>

编写生成类(可以在mybatis-plus官网直接copy):

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语句的执行时间。

具体步骤如下:

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

<dependency>
    <groupId>p6spy</groupId>
    <artifactId>p6spy</artifactId>
    <version>3.9.1</version>
</dependency>

在application.yml中修改配置

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

在resource下,创建spy.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_*

多数据源

先引入依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.1.0</version>
</dependency>

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

配置数据源

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

使用 @DS 切换数据源:

@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");
    }
}

到此这篇关于MyBatis-plus字段类型处理器、自动填充和乐观锁详解的文章就介绍到这了,更多相关MyBatis-plus字段类型处理器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文