Mybatis-Plus进阶之扩展插件

Mybatis-Plus进阶之扩展插件

MybatisPlus基础篇请看:Mbatis-Plus整合springboot详细学习笔记

基本实体类:

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Employee {

    // id
    private Long id;

    // 名称
    private String name;

    // 年龄
    @TableField(fill = FieldFill.UPDATE)
    private Integer age;

    // 邮箱
    private String email;

    // 上级id
    private Long managerId;

    /** 设置自动填充 */
    // 创建时间
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    // 更新时间
    @TableField(fill = FieldFill.UPDATE)
    private LocalDateTime updateTime;

    // 版本号
    @Version
    private Integer version;

    // 逻辑删除的 局部设置
    @TableField(select = false) // 查询的时候,不显示这个参数
    @TableLogic
    private Integer deleted;

    // 子级
    @TableField(exist = false)
    private List<Employee> children = new ArrayList<>();

}

1、逻辑删除

springboot中配置:

# 配置逻辑删除的值   在这里配置的是  删除的时候为1  未删除的时候为0
mybatis-plus:
  global-config:
    db-config:
      logic-not-delete-value: 0
      logic-delete-value: 1

需要在实体类的字段上加上逻辑删除的注解:

  // 删除的时候为1  未删除的时候为0
  @TableLogic
  private Integer deleted;

测试逻辑删除:

// update employee set deleted = 1 where id = ? and deleted = 0
boolean b = employeeService.removeById(1237753494614700033L);

// 查询输出后将不会显示刚刚被删除的那条  select * from employee where deleted = 1
List<Employee> list = employeeService.list(null);

// 并且只能更新未删除的
int update = employeeMapper.update(null, lambdaUpdateWrapper);

逻辑删除注意事项:

【注意: 自定义的查询方法,系统 不会 自动加上 逻辑删除条件,需要自己手动加上】

	在mapper中自定义的方法,需要自己手动加上逻辑删除限制条件

2、自动填充

设置自动填充需要在字段上加上注解:

    /** 设置自动填充 */
    // 创建时间
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    // 更新时间
    @TableField(fill = FieldFill.UPDATE)
    private LocalDateTime updateTime;

配置字段自动填充:

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    /**
     *  插入的时候自动填充
     * @param metaObject
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        // fieldName 是实体中的属性名称
        // 检查是否有这个属性
        boolean createTime = metaObject.hasSetter("createTime");
        if (createTime) {
            this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        }
    }

    /**
     * 修改的时候自动填充
     * @param metaObject
     */
    @Override
    public void updateFill(MetaObject metaObject) {
         /**
         *  先检查时候已经传入值了,若传入值了,则以传入值为准,就不再自动填充       
         */
        Object updateTime = getFieldValByName("updateTime", metaObject);
        // 若没有传入值才自动填充
        if (updateTime == null) {
            this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
        }
    }

}

测试自动填充以及注意事项:


Employee employee = new Employee();

// 此时添加员工数据的时候会自动赋值上创建时间
int rows = employeeMapper.insert(employee);

// 这里修改会自动填充修改时间
int rows = employeeMapper.updateById(employee);


// 注意这种情况下【不会】自动填充修改时间 
int update = employeeMapper.update(null, lambdaUpdateWrapper);


// 有实体传入的情况下,会自动填充修改时间
int update = employeeMapper.update(employee, lambdaUpdateWrapper);

3、乐观锁

配置乐观锁插件:

@Slf4j
@Configuration
public class MybatisPlusConfig {
    /**
     *  乐观锁插件
     * @return
     */
    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
        return new OptimisticLockerInterceptor();
    }
}

实体类中字段上加上乐观锁注解:

    // 版本号
    @Version
    private Integer version;

乐观锁测试:

boolean b = employeeService.updateById(employee);
// 第二个参数为,【查询】  重点
boolean update = employeeService.update(employee,lambdaQueryWrapper);

注意事项:

特别说明:

  • 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime

  • 整数类型下 newVersion = oldVersion + 1

  • newVersion 会回写到 entity

  • 仅支持 updateById(entity)update(entity, wrapper) 方法

  • update(entity, wrapper) 方法下, wrapper 不能复用!!!

4、执行sql分析打印

引入依赖:

  <!-- 执行sql 分析打印 依赖-->
        <dependency>
            <groupId>p6spy</groupId>
            <artifactId>p6spy</artifactId>
            <version>3.8.2</version>
        </dependency>

更改数据库配置:

spring:
  datasource:
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver
    url: jdbc:p6spy:mysql://127.0.0.1:3306/mybatisplus?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: root

spy.properties 配置:

#3.2.1以上使用
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
#3.2.1以下使用或者不配置
#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台  若想要输出到,文件中则将下面这句配置注释掉
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2

# 将日志输出到指定的位置
#logfile=log.log

注意事项:

    driver-class-name 为 p6spy 提供的驱动类
    url 前缀为 jdbc:p6spy 跟着冒号为对应数据库连接地址
    打印出sql为null,在excludecategories增加commit
    批量操作不打印sql,去除excludecategories中的batch
    批量操作打印重复的问题请使用MybatisPlusLogFactory (3.2.1新增)
    该插件有性能损耗,不建议生产环境使用

5、多租户实现

需要在分页配置类中配置:


@Slf4j
@Configuration
public class MybatisPlusConfig {
    /**
    *	分页配置中,设置多租户信息
    */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        
        ArrayList<ISqlParser> sqlParsers = new ArrayList<>();
        
        // 租户解析器
        TenantSqlParser tenantSqlParser = new TenantSqlParser();
        
        tenantSqlParser.setTenantHandler(new TenantHandler() {
            @Override
            public Expression getTenantId(boolean where) {
                // 租户的id,这里测试固定写死
                return new LongValue(1088248166370832385L);
            }
            // 多租户字段是什么
            @Override
            public String getTenantIdColumn() {
                // 这是表中的字段名,即 用于区分租户的字段
                return "manager_id";
            }

            // 是否需要排除某些表,不加租户字段
            @Override
            public boolean doTableFilter(String tableName) {
                // 为 角色 表的时候,不增加租户信息
                if("role".equals(tableName)){
                    // 不增加租户信息
                    return true;
                }
                return false;
            }
        });

        sqlParsers.add(tenantSqlParser);

        paginationInterceptor.setSqlParserList(sqlParsers);
        
        
        /
            【特定sql 过滤】,可用注解替换 
            @SqlParser(filter = true)true表示,不在此方法上增加租户信息
         /
        paginationInterceptor.setSqlParserFilter(new ISqlParserFilter() {
            @Override
            public boolean doFilter(MetaObject metaObject) {

                MappedStatement ms = SqlParserHelper.getMappedStatement(metaObject);

                if ("cn.mesmile.mybatisplusdemo.mapper.EmployeeMapper.selectAll"
                        .equals(ms.getId())) {
                    // 同时这个方法对 动态表明解析也会产生作用,也会过滤掉动态表名,只显示原来的表名
                    // 不增加租户信息
                    return true;
                }

                return false;
            }

        });
        
        return paginationInterceptor;
        
    }
}

过滤得到不使用多租户用户的方法:

@Component
public interface EmployeeMapper extends MyMapper<Employee> {

    /**
     *  这是自定义的查询方法
     * @param wrapper
     */
//    @SqlParser(filter = true) 该注解为true 设置该方法,不需要要加入租户信息
    @Select("select * from employee ${ew.customSqlSegment}")
    List<Employee> selectAll(@Param(Constants.WRAPPER)Wrapper<Employee> wrapper);

}

6、动态表名解析

配置动态表名解析器:

@Slf4j
@Configuration
public class MybatisPlusConfig {
    
    public static ThreadLocal<String> myTableName = new ThreadLocal<>();
    
    /**
     *  mybatis-plus 的分页配置里面配置 动态表名解析器
     * @return
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();

        ArrayList<ISqlParser> sqlParsers = new ArrayList<>();

        // 动态表名解析器
        DynamicTableNameParser dynamicTableNameParser = new DynamicTableNameParser();

        HashMap<String, ITableNameHandler> tableNameHandlerHashMap = new HashMap<>();

        // 第一参数是需要  动态替换的表名
        tableNameHandlerHashMap.put("employee", new ITableNameHandler() {
            @Override
            public String dynamicTableName(MetaObject metaObject, String sql, String tableName) {

                log.info("metaObject:  {}", metaObject);
                log.info("原表执行的 sql:  {}", sql);
                log.info("原表的名称 tableName:  {}", tableName);

                // 【这里返回动态的表名】
                // 如果返回的值为空,则不进行替换  这里返回的事 动态表名
                return myTableName.get();
            }

        });

        dynamicTableNameParser.setTableNameHandlerMap(tableNameHandlerHashMap);

        sqlParsers.add(dynamicTableNameParser);
        paginationInterceptor.setSqlParserList(sqlParsers);
        
        return paginationInterceptor;
    }
}

注意:【在多租户配置这里 , 过滤的方法, 动态表名不会生效】

/
            【特定sql 过滤】,可用注解替换 
            @SqlParser(filter = true)true表示,不在此方法上增加租户信息
         /
        paginationInterceptor.setSqlParserFilter(new ISqlParserFilter() {
            @Override
            public boolean doFilter(MetaObject metaObject) {

                MappedStatement ms = SqlParserHelper.getMappedStatement(metaObject);

                if ("cn.mesmile.mybatisplusdemo.mapper.EmployeeMapper.selectAll"
                        .equals(ms.getId())) {
                    // 同时这个方法对 动态表明解析也会产生作用,也会过滤掉动态表名,只显示原来的表名
                    // 不增加租户信息
                    return true;
                }

                return false;
            }

        });

例:

@Component
public interface EmployeeMapper extends BaseMapper<Employee> {

    /**
     *  这是自定义的查询方法
     * @param wrapper
     * @return
     */
    //该注解为true 设置该方法,不需要要加入租户信息
    //@SqlParser(filter = true) 
    @Select("select * from employee ${ew.customSqlSegment}")
    List<Employee> selectAll(@Param(Constants.WRAPPER)Wrapper<Employee> wrapper);

}

测试:


MybatisPlusConfig.myTableName.set("employee_2020");

LambdaQueryWrapper<Employee> employeeLambdaQueryWrapper = Wrappers.lambdaQuery();

// 当自定义查询方法[有] @SqlParser(filter = true) 时候:select * from employee 动态表名不生效
// 当自定义查询方法[没有] @SqlParser(filter = true) 时候:select * from employee_2020 动态表名生效
List<Employee> employees = employeeMapper.selectAll(employeeLambdaQueryWrapper);

7、sql注入器 和 选装件

public class DeleteAllMethod extends AbstractMethod {
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        // 执行的sql
        String sql = "delete from "+ tableInfo.getTableName();
        // mapper 接口方法名
        String method = "deleteAll";

        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);


        return addDeleteMappedStatement(mapperClass, method , sqlSource);
    }
}
@Component
public class MySqlInjector extends DefaultSqlInjector {

    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        // 获取到父类已有的抽象方法
        List<AbstractMethod> methodList = super.getMethodList(mapperClass);
        
        // 将自定义的类方法 加入到父类的集合中
        methodList.add(new DeleteAllMethod());

        return methodList;
    }
}

在mapper类中写入方法:


@Component
public interface EmployeeMapper extends BaseMapper<Employee> {

    /**
     *  自定义的sql注入方法,删除所有
     * @return 返回受影响行数
     */
    int deleteAll();
}

选装件:InsertBatchSomeColumn 、 LogicDeleteByIdWithFill 、AlwaysUpdateSomeColumnById

加入选装件:

@Component
public class MySqlInjector extends DefaultSqlInjector {

    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        
        List<AbstractMethod> methodList = super.getMethodList(mapperClass);
        // 将自定义的类方法 加入到父类的集合中
        methodList.add(new DeleteAllMethod());

        // 【选装件】 插入的时候,不插入【逻辑删除】 和 【age年龄】
        methodList.add(new InsertBatchSomeColumn(t -> !t.isLogicDelete() && !t.getColumn().equals("age")));

        // 【选装件】 逻辑删除的时候同时再修改值,但是修改值得字段上要加上注解 @TableField(fill = FieldFill.UPDATE)
        methodList.add(new LogicDeleteByIdWithFill());

        // 【选装件】 根据id更新固定的几个字段(但是不包括逻辑删除)  这里 不更新 name 字段
        methodList.add(new AlwaysUpdateSomeColumnById(t -> !t.getColumn().equals("name")));

        return methodList;
    }
}

在mapper接口中写选装件方法:

public interface MyMapper<T> extends BaseMapper<T> {

    /**
     *  自定义的sql注入方法,删除所有
     * @return 返回受影响行数
     */
    int deleteAll();

    /**
     *  mybatis-plus 中的【选装件】 , 自定义批量插入
     * @param list
     * @return
     */
    int insertBatchSomeColumn(List<T> list);

    /**
     * mybatis-plus 中的额【选装件】 , 自定义删除
     *
     *  在删除的同时,填充值修改值,需要在 修改的字段上 加注解
     *          @TableField(fill = FieldFill.UPDATE)
     *          private Integer age;
     * @param entity
     * @return
     */
    int deleteByIdWithFill(T entity);

    /**
     *  mybatis-plus 中的额【选装件】
     *      指定更新某些字段
     * @param entity
     * @return
     */
    int alwaysUpdateSomeColumnById(@Param(Constants.ENTITY) T entity);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值