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来标注将哪个变量的值插入到数据库。
-
先创建枚举类
javapublic 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类添加枚举属性
javapublic 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注解配合使用。
实体类代码如下:
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; // 联系方式
}
自动填充
在实际应用中,有一些属性,其实不需要我们每次都手动填充,可以设置为自动填充,比如创建时间、更新时间等可以设置为自动填充。
注意时区的设置:
- mysql数据库设置时区:set global time_zone = '+8:00'
查看时区对不对?执行select now() 看时间能不能对上。
- 项目中的时区设置:在数据库连接Url中设置。
yaml
url: jdbc:mysql://localhost:3306/spring6?useSSL=false&serverTimezone=UTC&characterEncoding=utf8&useUnicode=true
把serverTimezone=UTC改为serverTimezone=Asia/Shanghai。
- 使用@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;
}
-
编写自定义处理器设置填充策略
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); } }
此时在新增或更新时,无需手动设置创建时间更新时间,系统会自动填充时间到数据库表中。
防止全表更新与删除插件
配置拦截规则 :插件默认拦截没有指定条件的 update 和 delete 语句。
当触发拦截时,会抛出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框架。
-
MyBatisX先安装插件:
File -- > Settings -->Plugins -- >搜索 MyBatisX,然后安装
-
MyBatisX插件的快速定位:
这个插件把Mapper接口的方法和映射文件的sql进行了一一对应。可以从Mapper接口方法快速跳到对应的映射文件对应的sql。
同样,也可以在映射文件点击小鸟快速跳转到对应的Mapper接口
-
MyBatisX插件的逆向工程:
通过数据库表,快速生成以下项目文件:
-
实体类
-
Mapper接口
-
Mapper映射文件
-
Service接口
-
Service实现类
-
首先使用idea连接数据库:
-
选择对应的数据库表,然后右键选择MyBatisX-generate
-
-
-
最后一步设置
这样就生成了对应的项目文件。注意这里的Mapper接口文件要手动加上@Mapper注解。
乐观锁
并发请求就是在同一时刻有多个请求去请求同一个服务器资源。如果是获取信息,不会出现问题,但是如果做修改操作,就会出现并发问题。
比如:三个人去买同样的商品,商品剩余一件。购买时一般先查询库存再购买后数量减一,并发请求就是同一时刻,三个人都查到了商品剩余1个,然后同时进行购买。这样只能一个人买到,另外两人肯定买不到,此时就发生了超卖行为。这就是经典的并发问题。
常见的数据库锁类型有两种,悲观锁和乐观锁:
悲观锁:查询时就锁定数据,在请求完成之前不会释放锁。完成后才释放锁。释放锁以后,其他请求才可以对数据进行读写。
这样虽解决了并发问题,但是效率较低。实际开发中很少使用悲观锁。
乐观锁:通过表字段完成设计。乐观锁的核心思想是:在读取的时候不加锁,其他请求仍可以读取这个数据,在修改的时候,判断一个数据是否有被修改过,如果修改过,那本次请求的修改操作失效。
具体设计如下:
增加一个字段version。购买商品时先查询,此时能获取到version的值。
在执行购买操作时,更新库存数量前再查询一次version的值,如果两次的version值一样,表示可以进行更新库存操作,更新时进行 version = version +1,表示我执行了一次。这样就完成了乐观锁的操作。
如果两次的version 值不一致,说明有人对库存数据进行了更新,此时不能直接进行购买。需要重新进行先查询后购买的操作。
MyBatis-plus中乐观锁实现步骤如下:
-
在实体类中使用@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;// 版本 }-
在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; } }
-
-
测试代码:
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);
代码生成器
代码生成器和逆向功能的区别在于,代码生成器可以生成更多的结构,更多的内容,允许配置更多的内容。
具体步骤如下:
-
引入相关依赖
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> -
编写生成类(可以在mybatis-plus官网直接copy):
javapublic 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语句。然而,由于性能损耗,建议在生产环境中谨慎使用。所以先引入依赖:xml<dependency> <groupId>p6spy</groupId> <artifactId>p6spy</artifactId> <version>3.9.1</version> </dependency>-
在application.yml中修改配置
yamlspring: 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配置文件
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_*多数据源
-
先引入依赖
xml<dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.1.0</version> </dependency>
-
dynamic-datasource 是一个开源的 Spring Boot 多数据源启动器,提供了丰富的功能,包括数据源分组、敏感信息加密、独立初始化表结构等
-
配置数据源
yamlspring: 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切换数据源: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"); } }