找回密码
 立即注册
首页 业界区 业界 MyBatis-plus拓展之字段类型处理器、自动填充和乐观锁等 ...

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

赖琳芳 5 天前
MyBatis-plus拓展

逻辑删除

逻辑删除就是增加一个字段表示这个数据的状态,通过状态来显示数据或隐藏数据,而不是真正的删除。
MyBatis-plus使用@TableLogic注解来标注逻辑删除字段:
  1. public class User extends Model<User> {
  2.     @TableId
  3.     private Long id;
  4.     private String name;
  5.     private Integer age;
  6.     private String email;
  7.     // 配置当前字段为逻辑删除字段
  8.     // 默认值是1,删除状态的值是0
  9.     @TableLogic(value = "1",delval = "0")
  10.     private Integer status;
  11. }
复制代码
此时如果调用Mapper的删除方法,实际对应的sql语句是更新操作。将逻辑删除字段的值更新为0,而不是真正的删除。
而且此时Mapper的查询方法,不会查出这条数据。生成的sql语句会自动拼接where条件:status = 1
逻辑删除也可以在配置文件中进行全局配置:
  1. mybatis-plus:
  2.   global-config:
  3.     banner: false
  4.     db-config:
  5.       id-type: assign_id
  6.       # 逻辑删除字段为status
  7.       logic-delete-field: status
  8.       # 删除状态的值
  9.       logic-delete-value: 0
  10.       # 未删除状态的值
  11.       logic-not-delete-value: 1
复制代码
使用全局配置后就不用使用@TableLogic注解了。
通用枚举

假如要表示性别:只有男和女两个值,我们就可以使用枚举来描述。
数据库表中使用gender (int 类型)表示性别,0表示女性,1表示男性。
使用@EnumValue来标注将哪个变量的值插入到数据库。

  • 先创建枚举类
    1. public enum GenderEnum {
    2.     MAN(1,"男"),WOMAN(0,"女");
    3.    
    4.     @EnumValue // 表示将这个变量的值插入到数据库
    5.     private Integer gender;
    6.     private String genderName;
    7.     GenderEnum(Integer gender, String genderName) {
    8.         this.gender = gender;
    9.         this.genderName = genderName;
    10.     }
    11. }
    复制代码
  • 给Pojo类添加枚举属性
    1. public class User extends Model<User> {
    2.     @TableId
    3.     private Long id;
    4.     private String name;
    5.     private Integer age;
    6.     private String email;
    7.     // 配置当前字段为逻辑删除字段
    8.     // 默认值是1,删除状态的值是0
    9.     @TableLogic(value = "1",delval = "0")
    10.     private Integer status;
    11.     private GenderEnum gender;
    12. }
    复制代码

    • 调用正常的插入方法即可实现。

字段类型处理器

某些场景下,实体类中使用map集合作为属性接收前端传来的数据,但是把这些输出存到数据库时,使用json格式的字符串存储。那怎么把map类型转换成字符串类型呢?这里就需要使用字段类型处理器。
需要@TableName注解和@TableField注解配合使用。
实体类代码如下:
  1. // autoResultMap = true 表示查询时,自动将contact字段json格式的字符串转换为Map类型
  2. @TableName(autoResultMap = true)
  3. public class User extends Model<User> {
  4.     @TableId
  5.     private Long id;
  6.     private String name;
  7.     private Integer age;
  8.     private String email;
  9.     // 配置当前字段为逻辑删除字段
  10.     // 默认值是1,删除状态的值是0
  11.     @TableLogic(value = "1",delval = "0")
  12.     private Integer status;
  13.     private GenderEnum gender;
  14.     // 指定类型处理器,在新增或更新时,自动将Map类型转换成json格式的字符串
  15.     // 这个处理器依赖于Fastjson,所以要在pom文件中引入Fastjson依赖
  16.     @TableField(typeHandler = FastjsonTypeHandler.class)
  17.     private Map<String, String> contact; // 联系方式
  18. }
复制代码
自动填充

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

  • mysql数据库设置时区:set global time_zone = '+8:00'
查看时区对不对?执行select now() 看时间能不能对上。

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

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

  • 编写自定义处理器设置填充策略
    1. //自定义Handler,设置填充策略,这里需要实现MetaObjectHandler接口
    2. @Component
    3. public class MyMetaHandler implements MetaObjectHandler {
    4.     // 插入时的填充策略
    5.     @Override
    6.     public void insertFill(MetaObject metaObject) {
    7.         setFieldValByName("creatTime",new Date(),metaObject);
    8.         setFieldValByName("modifyTime",new Date(),metaObject);
    9.     }
    10.     // 更新时的填充策略
    11.     @Override
    12.     public void updateFill(MetaObject metaObject) {
    13.         setFieldValByName("modifyTime",new Date(),metaObject);
    14.     }
    15. }
    复制代码
此时在新增或更新时,无需手动设置创建时间更新时间,系统会自动填充时间到数据库表中。
防止全表更新与删除插件

配置拦截规则:插件默认拦截没有指定条件的 update 和 delete 语句。
当触发拦截时,会抛出MyBatisPlusException异常。
通过在MybatisPlusConfig 这个配置类中加入对应的拦截器来阻止全表更新与删除:
  1. @Configuration
  2. @MapperScan("com.ali.mapper")
  3. public class MybatisPlusConfig {
  4.     @Bean
  5.     public MybatisPlusInterceptor mybatisPlusInterceptor() {
  6.         MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
  7.         // 防止全表更新与删除插件
  8.         interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
  9.         interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加
  10.         // 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType
  11.         return interceptor;
  12.     }
  13. }
复制代码
MyBatisX快速开发插件

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

  • MyBatisX先安装插件:
    File -- > Settings -->lugins -- >搜索 MyBatisX,然后安装
  • MyBatisX插件的快速定位:
    这个插件把Mapper接口的方法和映射文件的sql进行了一一对应。可以从Mapper接口方法快速跳到对应的映射文件对应的sql。
1.png

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

  • MyBatisX插件的逆向工程:
    通过数据库表,快速生成以下项目文件:

    • 实体类
    • Mapper接口
    • Mapper映射文件
    • Service接口
    • Service实现类

      • 首先使用idea连接数据库:


2.png
  1. 2. 选择对应的数据库表,然后右键选择MyBatisX-generate
复制代码
3.png


  • 最后一步设置
4.png

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

并发请求就是在同一时刻有多个请求去请求同一个服务器资源。如果是获取信息,不会出现问题,但是如果做修改操作,就会出现并发问题。
比如:三个人去买同样的商品,商品剩余一件。购买时一般先查询库存再购买后数量减一,并发请求就是同一时刻,三个人都查到了商品剩余1个,然后同时进行购买。这样只能一个人买到,另外两人肯定买不到,此时就发生了超卖行为。这就是经典的并发问题。
常见的数据库锁类型有两种,悲观锁和乐观锁:
悲观锁:查询时就锁定数据,在请求完成之前不会释放锁。完成后才释放锁。释放锁以后,其他请求才可以对数据进行读写。
这样虽解决了并发问题,但是效率较低。实际开发中很少使用悲观锁。
乐观锁:通过表字段完成设计。乐观锁的核心思想是:在读取的时候不加锁,其他请求仍可以读取这个数据,在修改的时候,判断一个数据是否有被修改过,如果修改过,那本次请求的修改操作失效。
具体设计如下:
增加一个字段version。购买商品时先查询,此时能获取到version的值。
在执行购买操作时,更新库存数量前再查询一次version的值,如果两次的version值一样,表示可以进行更新库存操作,更新时进行 version = version +1,表示我执行了一次。这样就完成了乐观锁的操作。
如果两次的version 值不一致,说明有人对库存数据进行了更新,此时不能直接进行购买。需要重新进行先查询后购买的操作。
MyBatis-plus中乐观锁实现步骤如下:

  • 在实体类中使用@Version注解指定版本字段
    1. // autoResultMap = true 表示查询时,自动将contact字段json格式的字符串转换为Map类型
    2. @TableName(autoResultMap = true)
    3. public class User extends Model<User> {
    4.     @TableId
    5.     private Long id;
    6.     private String name;
    7.     private Integer age;
    8.     private String email;
    9.     // 配置当前字段为逻辑删除字段
    10.     // 默认值是1,删除状态的值是0
    11.     @TableLogic(value = "1",delval = "0")
    12.     private Integer status;
    13.     private GenderEnum gender;
    14.     // 指定类型处理器,在新增或更新时,自动将Map类型转换成json格式的字符串
    15.     // 这个处理器依赖于Fastjson,所以要在pom文件中引入Fastjson依赖
    16.     @TableField(typeHandler = FastjsonTypeHandler.class)
    17.     private Map<String, String> contact; // 联系方式
    18.     // 指定插入时填充
    19.     @TableField(fill = FieldFill.INSERT)
    20.     private Data creatTime;
    21.     // 指定插入或更新时填充
    22.     @TableField(fill = FieldFill.INSERT_UPDATE)
    23.     private Data modifyTime;
    24.     @Version // 表示将此字段作为版本信息
    25.     private Integer version;// 版本
    26. }
    复制代码

    • 在MybatisPlusConfig配置类中加入乐观锁拦截器
      1. @Configuration
      2. @MapperScan("com.ali.mapper")
      3. public class MybatisPlusConfig {
      4.     @Bean
      5.     public MybatisPlusInterceptor mybatisPlusInterceptor() {
      6.         MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
      7.         // 乐观锁拦截器
      8.         interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
      9.         // 防止全表更新与删除插件
      10.         interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
      11.         interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加
      12.         // 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType
      13.         return interceptor;
      14.     }
      15. }
      复制代码

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

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

  • 引入相关依赖
    1.         <dependency>
    2.             <groupId>com.baomidou</groupId>
    3.             mybatis-plus-generator</artifactId>
    4.             <version>3.5.3</version>
    5.         </dependency>
    6.         
    7.         <dependency>
    8.             <groupId>org.freemarker</groupId>
    9.             freemarker</artifactId>
    10.             <version>2.3.31</version>
    11.         </dependency>
    复制代码
  • 编写生成类(可以在mybatis-plus官网直接copy):
    1. public class CodeGeneratorTest {
    2.     public static void main(String[] args) {
    3.         // 使用 FastAutoGenerator 快速配置代码生成器
    4.         FastAutoGenerator.create("jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8", "root", "password")
    5.                 .globalConfig(builder -> {
    6.                     builder.author("Your Name") // 设置作者
    7.                             .outputDir("src/main/java"); // 输出目录
    8.                 })
    9.                 .packageConfig(builder -> {
    10.                     builder.parent("com.example") // 设置父包名
    11.                             .entity("model") // 设置实体类包名
    12.                             .mapper("dao") // 设置 Mapper 接口包名
    13.                             .service("service") // 设置 Service 接口包名
    14.                             .serviceImpl("service.impl") // 设置 Service 实现类包名
    15.                             .xml("mappers"); // 设置 Mapper XML 文件包名
    16.                 })
    17.                 .strategyConfig(builder -> {
    18.                     builder.addInclude("table1", "table2") // 设置需要生成的表名
    19.                             .entityBuilder()
    20.                             .enableLombok() // 启用 Lombok
    21.                             .enableTableFieldAnnotation() // 启用字段注解
    22.                             .controllerBuilder()
    23.                             .enableRestStyle(); // 启用 REST 风格
    24.                 })
    25.                 .templateEngine(new FreemarkerTemplateEngine()) // 使用 Freemarker 模板引擎
    26.                 .execute(); // 执行生成
    27.     }
    28. }
    复制代码
    执行sql打印分析

    通过sql分析来获取sql语句的执行时间。
    具体步骤如下:

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

      • 在application.yml中修改配置
        1. spring:
        2.   datasource:
        3.     username: root
        4.     password: root
        5.     url: jdbc:p6spy:mysql://localhost:3306/spring6?useSSL=false&serverTimezone=UTC&characterEncoding=utf8&useUnicode=true
        6.     driver-class-name: com.p6spy.engine.spy.P6SpyDriver
        复制代码


  • 在resource下,创建spy.properties配置文件
    1. # 模块列表,根据版本选择合适的配置
    2. modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
    3. # 自定义日志格式
    4. logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
    5. # 日志输出到控制台
    6. appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
    7. # 取消JDBC驱动注册
    8. deregisterdrivers=true
    9. # 使用前缀
    10. useprefix=true
    11. # 排除的日志类别
    12. excludecategories=info,debug,result,commit,resultset
    13. # 日期格式
    14. dateformat=yyyy-MM-dd HH:mm:ss
    15. # 实际驱动列表
    16. # driverlist=org.h2.Driver
    17. # 开启慢SQL记录
    18. outagedetection=true
    19. # 慢SQL记录标准(单位:秒)
    20. outagedetectioninterval=2
    21. # 过滤 flw_ 开头的表 SQL 打印
    22. filter=true
    23. exclude=flw_*
    复制代码
    多数据源

    • 先引入依赖
      1. <dependency>
      2.     <groupId>com.baomidou</groupId>
      3.     dynamic-datasource-spring-boot-starter</artifactId>
      4.     <version>3.1.0</version>
      5. </dependency>
      复制代码

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

  • 配置数据源
    1. spring:
    2.   datasource:
    3.     dynamic:
    4.     # 默认数据源名为 master,可通过 spring.datasource.dynamic.primary 修改。
    5.       primary: master
    6.       strict: false
    7.       datasource:
    8.         master:
    9.           url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
    10.           username: root
    11.           password: 123456
    12.           driver-class-name: com.mysql.jdbc.Driver
    13.         slave_1:
    14.           url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
    15.           username: root
    16.           password: 123456
    17.           driver-class-name: com.mysql.jdbc.Driver
    18.         slave_2:
    19.           url: ENC(xxxxx)
    20.           username: ENC(xxxxx)
    21.           password: ENC(xxxxx)
    22.           driver-class-name: com.mysql.jdbc.Driver
    复制代码
  • 使用 @DS 切换数据源
    1. @Service
    2. @DS("master")
    3. public class UserServiceImpl implements UserService {
    4.     @Autowired
    5.     private JdbcTemplate jdbcTemplate;
    6.     @Override
    7.     @DS("slave_1")
    8.     public List selectByCondition() {
    9.         return jdbcTemplate.queryForList("select * from user where age >10");
    10.     }
    11. }
    复制代码

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册