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; // 联系方式
- }
复制代码 自动填充
在实际应用中,有一些属性,其实不需要我们每次都手动填充,可以设置为自动填充,比如创建时间、更新时间等可以设置为自动填充。
注意时区的设置:
- mysql数据库设置时区:set global time_zone = '+8:00'
查看时区对不对?执行select now() 看时间能不能对上。
- url: jdbc:mysql://localhost:3306/spring6?useSSL=false&serverTimezone=UTC&characterEncoding=utf8&useUnicode=true
复制代码 把serverTimezone=UTC改为serverTimezone=Asia/Shanghai。
- // 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);
- }
- }
复制代码 此时在新增或更新时,无需手动设置创建时间更新时间,系统会自动填充时间到数据库表中。
防止全表更新与删除插件
配置拦截规则:插件默认拦截没有指定条件的 update 和 delete 语句。
当触发拦截时,会抛出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框架。
- MyBatisX先安装插件:
File -- > Settings --> lugins -- >搜索 MyBatisX,然后安装
- MyBatisX插件的快速定位:
这个插件把Mapper接口的方法和映射文件的sql进行了一一对应。可以从Mapper接口方法快速跳到对应的映射文件对应的sql。
同样,也可以在映射文件点击小鸟快速跳转到对应的Mapper接口
- MyBatisX插件的逆向工程:
通过数据库表,快速生成以下项目文件:
- 实体类
- Mapper接口
- Mapper映射文件
- Service接口
- Service实现类
- 2. 选择对应的数据库表,然后右键选择MyBatisX-generate
复制代码
这样就生成了对应的项目文件。注意这里的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>
- mybatis-plus-generator</artifactId>
- <version>3.5.3</version>
- </dependency>
-
- <dependency>
- <groupId>org.freemarker</groupId>
- 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>
- 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>
- 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");
- }
- }
复制代码
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |