第 8 章 MybatisPlus 扩展

第 8 章 MybatisPlus 扩展

1、前置说明

关于 MybatisPlus 扩展的说明

emmm,这里我就不肝原理,只写应用吧。。。字数太多了,Typora 都被我肝卡了,实在是肝不动了。。。

2、逻辑删除

逻辑删除的介绍

只对自动注入的sql起效

  • 插入: 不作限制
  • 查找: 追加where条件过滤掉已删除数据,且使用 wrapper.entity 生成的where条件会忽略该字段
  • 更新: 追加where条件防止更新到已删除数据,且使用 wrapper.entity 生成的where条件会忽略该字段
  • 删除: 转变为 更新

例如:

  • 删除: update user set deleted=1 where id = 1 and deleted=0
  • 查找: select id,name,deleted from user where deleted=0

字段类型支持说明

  • 支持所有数据类型(推荐使用 Integer,Boolean,LocalDateTime)
  • 如果数据库字段使用datetime,逻辑未删除值和已删除值支持配置为字符串null,另一个值支持配置为函数来获取值如now()

附录

  • 逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。
  • 如果你需要频繁查出来看就不应使用逻辑删除,而是以一个状态去表示。

逻辑删除的使用步骤

User 实体类中增加 deleted 字段,并用 @TableLogic 注解标识这是一个逻辑删除字段

/**
 * @Author Oneby
 * @Date 2021/4/18 17:53
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_user")
public class User extends Model<User> {

    @TableId(type = IdType.AUTO)
    private Long id;

    @TableField("username")
    private String name;

    private Integer age;

    private String email;

    @Version
    private Integer version;

    @TableLogic
    private Integer deleted;

}

t_user 表中添加 deleted 列,对于逻辑删除列,最好都添加上一个默认值(逻辑未删除的值)

image-20210425083504851

在 application.yml 配置文件中设置 logic-delete-value(逻辑已删除值)和 logic-not-delete-value(逻辑未删除值)

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: flag  # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1     # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

测试代码

@Test
public void testLogicDelete() {
    int count = userMapper.deleteById(1);
    System.out.println("影响行数:" + count);
}

从 SQL 日志可以看到,删除操作变成了更新操作:UPDATE t_user SET deleted=1 WHERE id=? AND deleted=0

Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3c87e6b7] was not registered for synchronization because synchronization is not active
2021-04-25 08:21:52.228  INFO 9828 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-04-25 08:21:52.388  INFO 9828 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@1069163325 wrapping com.mysql.cj.jdbc.ConnectionImpl@427ae189] will not be managed by Spring
==>  Preparing: UPDATE t_user SET deleted=1 WHERE id=? AND deleted=0
==> Parameters: 1(Integer)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3c87e6b7]
影响行数:1

逻辑删除的原理

我们在 application.yml 配置文件中设置的值会绑定到 GlobalConfig 全局配置类的 logicDeleteFieldlogicDeleteValuelogicNotDeleteValue 字段中

/**
 * Mybatis 全局缓存
 *
 * @author Caratacus
 * @since 2016-12-06
 */
@Data
@Accessors(chain = true)
@SuppressWarnings("serial")
public class GlobalConfig implements Serializable {
    
    /**
     * 逻辑删除全局属性名
     */
    private String logicDeleteField;
    /**
     * 逻辑删除全局值(默认 1、表示已删除)
     */
    private String logicDeleteValue = "1";
    /**
     * 逻辑未删除全局值(默认 0、表示未删除)
     */
    private String logicNotDeleteValue = "0";

TableFieldInfo 类对应于数据库表的字段信息:logicDelete 表示该字段是否为逻辑删除字段;logicDeleteValue 为逻辑删除值;logicNotDeleteValue 逻辑未删除值

/**
 * 数据库表字段反射信息
 *
 * @author hubin sjy willenfoo tantan
 * @since 2016-09-09
 */
@Getter
@ToString
@EqualsAndHashCode
@SuppressWarnings("serial")
public class TableFieldInfo implements Constants {
    
    /**
     * 是否是逻辑删除字段
     */
    private boolean logicDelete = false;
    /**
     * 逻辑删除值
     */
    private String logicDeleteValue;
    /**
     * 逻辑未删除值
     */
    private String logicNotDeleteValue;
    
    /**
     * 逻辑删除初始化
     *
     * @param dbConfig 数据库全局配置
     * @param field    字段属性对象
     */
    private void initLogicDelete(GlobalConfig.DbConfig dbConfig, Field field, boolean existTableLogic) {
        /* 获取注解属性,逻辑处理字段 */
        TableLogic tableLogic = field.getAnnotation(TableLogic.class);
        if (null != tableLogic) {
            if (StringUtils.isNotBlank(tableLogic.value())) {
                this.logicNotDeleteValue = tableLogic.value();
            } else {
                this.logicNotDeleteValue = dbConfig.getLogicNotDeleteValue();
            }
            if (StringUtils.isNotBlank(tableLogic.delval())) {
                this.logicDeleteValue = tableLogic.delval();
            } else {
                this.logicDeleteValue = dbConfig.getLogicDeleteValue();
            }
            this.logicDelete = true;
        } else if (!existTableLogic) {
            String deleteField = dbConfig.getLogicDeleteField();
            if (StringUtils.isNotBlank(deleteField) && this.property.equals(deleteField)) {
                this.logicNotDeleteValue = dbConfig.getLogicNotDeleteValue();
                this.logicDeleteValue = dbConfig.getLogicDeleteValue();
                this.logicDelete = true;
            }
        }
    }

我们来看看 DeleteById 类中注入 SQL 语句的逻辑

  1. 如果if (tableInfo.isWithLogicDelete()) 成立,即表中有逻辑删除字段,MybatisPlus 会将删除操作变为更新操作:addUpdateMappedStatement(mapperClass, modelClass, getMethod(sqlMethod), sqlSource)
  2. 否则if (tableInfo.isWithLogicDelete()) 成立,即表中没有逻辑删除字段,MybatisPlus 会注入删除操作的 SQL 语句:this.addDeleteMappedStatement(mapperClass, getMethod(sqlMethod), sqlSource)
/**
 * 根据 ID 删除
 *
 * @author hubin
 * @since 2018-04-06
 */
public class DeleteById extends AbstractMethod {

    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        String sql;
        SqlMethod sqlMethod = SqlMethod.LOGIC_DELETE_BY_ID;
        if (tableInfo.isWithLogicDelete()) {
            sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), sqlLogicSet(tableInfo),
                tableInfo.getKeyColumn(), tableInfo.getKeyProperty(),
                tableInfo.getLogicDeleteSql(true, true));
            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, Object.class);
            return addUpdateMappedStatement(mapperClass, modelClass, getMethod(sqlMethod), sqlSource);
        } else {
            sqlMethod = SqlMethod.DELETE_BY_ID;
            sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), tableInfo.getKeyColumn(),
                tableInfo.getKeyProperty());
            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, Object.class);
            return this.addDeleteMappedStatement(mapperClass, getMethod(sqlMethod), sqlSource);
        }
    }
}

UserMapper#deleteById() 方法注入的 SQL 语句为 "UPDATE t user SET deleted=1 WHEREid=? AND deleted=0,删除操作变为对逻辑字段的更新操作

image-20210425085045120

3、SQL 注入器

SQL 注入器使用步骤

编写自定义注入方法:创建 DeleteAllMysqlInsertAllBatch 类,继承自 AbstractMethod 抽象父类,并重写其中的 injectMappedStatement() 方法,为 Mapper 接口的方法注入其对应的 SQL 语句

/**
 * 删除全部
 *
 * @Author Oneby
 * @Date 2021/4/27 22:04
 */
public class DeleteAll extends AbstractMethod {

    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        /* 执行 SQL ,动态 SQL 参考类 SqlMethod */
        String sql = "delete from " + tableInfo.getTableName();
        /* mapper 接口方法名一致 */
        String method = "deleteAll";
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return this.addDeleteMappedStatement(mapperClass, method, sqlSource);
    }

}

/**
 * @Author Oneby
 * @Date 2021/4/27 22:07
 */
public class MysqlInsertAllBatch extends AbstractMethod {

    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        final String sql = "<script>insert into %s %s values %s</script>";
        final String fieldSql = prepareFieldSql(tableInfo);
        final String valueSql = prepareValuesSqlForMysqlBatch(tableInfo);
        final String sqlResult = String.format(sql, tableInfo.getTableName(), fieldSql, valueSql);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sqlResult, modelClass);
        return this.addInsertMappedStatement(mapperClass, modelClass, "mysqlInsertAllBatch", sqlSource, new NoKeyGenerator(), null, null);
    }

    private String prepareFieldSql(TableInfo tableInfo) {
        StringBuilder fieldSql = new StringBuilder();
        fieldSql.append(tableInfo.getKeyColumn()).append(",");
        tableInfo.getFieldList().forEach(x -> {
            fieldSql.append(x.getColumn()).append(",");
        });
        fieldSql.delete(fieldSql.length() - 1, fieldSql.length());
        fieldSql.insert(0, "(");
        fieldSql.append(")");
        return fieldSql.toString();
    }

    private String prepareValuesSqlForMysqlBatch(TableInfo tableInfo) {
        final StringBuilder valueSql = new StringBuilder();
        valueSql.append("<foreach collection=\"list\" item=\"item\" index=\"index\" open=\"(\" separator=\"),(\" close=\")\">");
        valueSql.append("#{item.").append(tableInfo.getKeyProperty()).append("},");
        tableInfo.getFieldList().forEach(x -> valueSql.append("#{item.").append(x.getProperty()).append("},"));
        valueSql.delete(valueSql.length() - 1, valueSql.length());
        valueSql.append("</foreach>");
        return valueSql.toString();
    }
}

注册自定义方法:创建 MyLogicSqlInjector 类,继承自 DefaultSqlInjector 类,并重写其中的 getMethodList() 方法

/**
 * 自定义 SqlInjector
 *
 * @Author Oneby
 * @Date 2021/4/27 22:06
 */
public class MyLogicSqlInjector extends DefaultSqlInjector {

    /**
     * 如果只需增加方法,保留MP自带方法
     * 可以super.getMethodList() 再add
     * @return
     */
    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        List<AbstractMethod> methodList = super.getMethodList(mapperClass);
        methodList.add(new DeleteAll());
        methodList.add(new MysqlInsertAllBatch());
        return methodList;
    }
    
}

自定义 MyBaseMapper 接口,继承自 BaseMapper 接口,并在其中添加我们自定义的方法

/**
 * @Author Oneby
 * @Date 2021/4/27 22:10
 */
public interface MyBaseMapper<T> extends BaseMapper<T> {

    /**
     * 自定义通用方法
     */
    Integer deleteAll();

    /**
     * 如果要自动填充,@{@code Param}(xx) xx参数名必须是 list/collection/array 3个的其中之一
     *
     * @param batchList
     * @return
     */
    int mysqlInsertAllBatch(@Param("list") List<T> batchList);

}

/**
 * @Author Oneby
 * @Date 2021/4/18 17:53
 */
@Repository
public interface UserMapper extends MyBaseMapper<User> {

}

MybatisPlusConfig 配置类中注册 MyLogicSqlInjector 组件

/**
 * @Author Oneby
 * @Date 2021/4/24 18:42
 */
@Configuration
@MapperScan("com.oneby.mapper")
public class MybatisPlusConfig {

    /**
     * 自定义 SqlInjector
     * 里面包含自定义的全局方法
     */
    @Bean
    public MyLogicSqlInjector myLogicSqlInjector() {
        return new MyLogicSqlInjector();
    }

}

测试代码

@Test
public void deleteAll() {
    Integer count = userMapper.deleteAll();
    System.out.println("影响行数:" + count);
}

@Test
public void mysqlInsertAllBatch() {
    User oneby = new User();
    oneby.setName("Oneby");
    oneby.setAge(21);
    oneby.setVersion(0);
    oneby.setDeleted(0);
    User heygo = new User();
    heygo.setName("Heygo");
    heygo.setEmail("Heygo@baomidou.com");
    heygo.setVersion(0);
    heygo.setDeleted(0);
    Integer count = userMapper.mysqlInsertAllBatch(Arrays.asList(oneby, heygo));
    System.out.println("影响行数:" + count);
}

SQL 日志:deleteAll() 方法对应的 SQL 语句为 delete from t_usermysqlInsertAllBatch() 方法插入的字段为:into t_user (id,username,age,email,version,deleted) values ( ?,?,?,?,?,? )

Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@25ad4f71] was not registered for synchronization because synchronization is not active
2021-04-27 22:13:02.768  INFO 18360 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-04-27 22:13:02.934  INFO 18360 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@220040730 wrapping com.mysql.cj.jdbc.ConnectionImpl@5434e40c] will not be managed by Spring
==>  Preparing: delete from t_user
==> Parameters: 
<==    Updates: 6
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@25ad4f71]
影响行数:6

Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6f94a5a5] was not registered for synchronization because synchronization is not active
2021-04-27 22:36:53.357  INFO 8312 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-04-27 22:36:53.482  INFO 8312 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@914293025 wrapping com.mysql.cj.jdbc.ConnectionImpl@7da39774] will not be managed by Spring
==>  Preparing: insert into t_user (id,username,age,email,version,deleted) values ( ?,?,?,?,?,? ),( ?,?,?,?,?,? )
==> Parameters: 6(Long), Oneby(String), 21(Integer), null, 0(Integer), 0(Integer), 7(Long), Heygo(String), null, Heygo@baomidou.com(String), 0(Integer), 0(Integer)
<==    Updates: 2
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6f94a5a5]
影响行数:2

SQL 注入器的原理分析

ISqlInjector 接口:ISqlInjector#inspectInject() 方法实现自定义方法的注入

/**
 * SQL 自动注入器接口
 *
 * @author hubin
 * @since 2016-07-24
 */
public interface ISqlInjector {

    /**
     * 检查SQL是否注入(已经注入过不再注入)
     *
     * @param builderAssistant mapper 信息
     * @param mapperClass      mapper 接口的 class 对象
     */
    void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass);
}

AbstractSqlInjector 抽象类:AbstractSqlInjector#inspectInject() 方法已经实现了 SQL 自动注入的逻辑,然后人家还留了个接口给我们使用:public abstract List<AbstractMethod> getMethodList(Class<?> mapperClass);,我们只需要在 getMethodList() 添加需要注入的方法即可,多么人性化

/**
 * SQL 自动注入器
 *
 * @author hubin
 * @since 2018-04-07
 */
public abstract class AbstractSqlInjector implements ISqlInjector {

    private static final Log logger = LogFactory.getLog(AbstractSqlInjector.class);

    @Override
    public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
        Class<?> modelClass = extractModelClass(mapperClass);
        if (modelClass != null) {
            String className = mapperClass.toString();
            Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());
            if (!mapperRegistryCache.contains(className)) {
                List<AbstractMethod> methodList = this.getMethodList(mapperClass);
                if (CollectionUtils.isNotEmpty(methodList)) {
                    TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
                    // 循环注入自定义方法
                    methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));
                } else {
                    logger.debug(mapperClass.toString() + ", No effective injection method was found.");
                }
                mapperRegistryCache.add(className);
            }
        }
    }

    /**
     * <p>
     * 获取 注入的方法
     * </p>
     *
     * @param mapperClass 当前mapper
     * @return 注入的方法集合
     * @since 3.1.2 add  mapperClass
     */
    public abstract List<AbstractMethod> getMethodList(Class<?> mapperClass);

    /**
     * 提取泛型模型,多泛型的时候请将泛型T放在第一位
     *
     * @param mapperClass mapper 接口
     * @return mapper 泛型
     */
    protected Class<?> extractModelClass(Class<?> mapperClass) {
        Type[] types = mapperClass.getGenericInterfaces();
        ParameterizedType target = null;
        for (Type type : types) {
            if (type instanceof ParameterizedType) {
                Type[] typeArray = ((ParameterizedType) type).getActualTypeArguments();
                if (ArrayUtils.isNotEmpty(typeArray)) {
                    for (Type t : typeArray) {
                        if (t instanceof TypeVariable || t instanceof WildcardType) {
                            break;
                        } else {
                            target = (ParameterizedType) type;
                            break;
                        }
                    }
                }
                break;
            }
        }
        return target == null ? null : (Class<?>) target.getActualTypeArguments()[0];
    }
}

DefaultSqlInjector 类:MybatisPlus 的默认 SQL 注入器,注入了 BaseMapper 中所有的方法

/**
 * SQL 默认注入器
 *
 * @author hubin
 * @since 2018-04-10
 */
public class DefaultSqlInjector extends AbstractSqlInjector {

    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        return Stream.of(
            new Insert(),
            new Delete(),
            new DeleteByMap(),
            new DeleteById(),
            new DeleteBatchByIds(),
            new Update(),
            new UpdateById(),
            new SelectById(),
            new SelectBatchByIds(),
            new SelectByMap(),
            new SelectOne(),
            new SelectCount(),
            new SelectMaps(),
            new SelectMapsPage(),
            new SelectObjs(),
            new SelectList(),
            new SelectPage()
        ).collect(toList());
    }
}

我们自定义 SQL 注入器时只需要继承 DefaultSqlInjector,重写 getMethodList() 方法,往 methodList 集合里面塞方法就行啦

/**
 * 自定义 SqlInjector
 *
 * @Author Oneby
 * @Date 2021/4/27 22:06
 */
public class MyLogicSqlInjector extends DefaultSqlInjector {

    /**
     * 如果只需增加方法,保留MP自带方法
     * 可以super.getMethodList() 再add
     * @return
     */
    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        List<AbstractMethod> methodList = super.getMethodList(mapperClass);
        methodList.add(new DeleteAll());
        methodList.add(new MysqlInsertAllBatch());
        return methodList;
    }

}

4、通用枚举

TODO 以后空了,需要用到该功能的时候再来补笔记

5、自动填充功能

自动填充功能的使用步骤

t_user 表中新增 create_time 列,在 User 实体类中新增 createTime 字段,并标注 @TableField(fill = FieldFill.INSERT) 注解,表示在插入时需要自动填充该字段

/**
 * @Author Oneby
 * @Date 2021/4/18 17:53
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_user")
public class User extends Model<User> {

    @TableId(type = IdType.AUTO)
    private Long id;

    @TableField("username")
    private String name;

    private Integer age;

    private String email;

    @Version
    private Integer version;

    @TableLogic
    private Integer deleted;

    @TableField(fill = FieldFill.INSERT)
    private Date createTime;

}

创建 MyMetaObjectHandler 类,实现元对象处理器接口:com.baomidou.mybatisplus.core.handlers.MetaObjectHandler,并重写其中的 insertFill()updateFill() 方法,指定要填充元对象的哪个字段,用什么值去填充

/**
 * @Author Oneby
 * @Date 2021/4/28 21:55
 */
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill ....");
        this.strictInsertFill(metaObject, "createTime", Date.class, new Date()); // 起始版本 3.3.0(推荐使用)
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill ....");
        this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date()); // 起始版本 3.3.0(推荐使用)
    }

}

测试代码

@Test
public void autoFill() {
    User user = new User();
    user.setName("NiNiu");
    user.setAge(21);
    user.setEmail("NiNiu@baomidou.com");
    user.setVersion(0);
    user.setDeleted(0);
    int count = userMapper.insert(user);
    System.out.println("影响行数:" + count);
    System.out.println(user);
}

SQL 日志:虽然我们没有填充 createTime 字段的值,但是 MybatisPlus 帮我们自动填充啦

Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@53e800f9] was not registered for synchronization because synchronization is not active
2021-04-28 22:08:20.348  INFO 9880 --- [           main] com.oneby.handler.MyMetaObjectHandler    : start insert fill ....
2021-04-28 22:08:20.348  INFO 9880 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-04-28 22:08:20.489  INFO 9880 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@987834065 wrapping com.mysql.cj.jdbc.ConnectionImpl@46a488c2] will not be managed by Spring
==>  Preparing: INSERT INTO t_user ( username, age, email, version, deleted, create_time ) VALUES ( ?, ?, ?, ?, ?, ? )
==> Parameters: NiNiu(String), 21(Integer), NiNiu@baomidou.com(String), 0(Integer), 0(Integer), 2021-04-28 22:08:20.348(Timestamp)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@53e800f9]
影响行数:1
User(id=10, name=NiNiu, age=21, email=NiNiu@baomidou.com, version=0, deleted=0, createTime=Wed Apr 28 22:08:20 GMT+08:00 2021)

自动填充功能的原理分析

  1. 填充原理是直接给entity的属性设置值!!!
  2. 注解则是指定该属性在对应情况下必有值,如果无值则入库会是null
  3. MetaObjectHandler提供的默认方法的策略均为:如果属性有值则不覆盖,如果填充值为null则不填充
  4. 字段必须声明TableField注解,属性fill选择对应策略,该声明告知Mybatis-Plus需要预留注入SQL字段
  5. 填充处理器MyMetaObjectHandler在 Spring Boot 中需要声明@Component@Bean注入
  6. 要想根据注解FieldFill.xxx字段名以及字段类型来区分必须使用父类的strictInsertFill或者strictUpdateFill方法
  7. 不需要根据任何来区分可以使用父类的fillStrategy方法
/**
 * 字段填充策略枚举类
 *
 * <p>
 * 判断注入的 insert 和 update 的 sql 脚本是否在对应情况下忽略掉字段的 if 标签生成
 * <if test="...">......</if>
 * 判断优先级比 {@link FieldStrategy} 高
 * </p>
 *
 * @author hubin
 * @since 2017-06-27
 */
public enum FieldFill {
    /**
     * 默认不处理
     */
    DEFAULT,
    /**
     * 插入时填充字段
     */
    INSERT,
    /**
     * 更新时填充字段
     */
    UPDATE,
    /**
     * 插入和更新时填充字段
     */
    INSERT_UPDATE
}

在我们自己写的 MyMetaObjectHandler 类中,调用 this.strictInsertFill(metaObject, "createTime", Date.class, new Date()); 实现插入操作的自动填充,调用 this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date()); 实现更新操作的自动填充

这两个方法最终都会调用 strictFill() 方法:遍历需要自动填充的字段,去 tableInfo.getFieldList() 中匹配,如果匹配到了,就调用 strictFillStrategy(metaObject, fieldName, i.getFieldVal())) 方法实现自动填充

strictFillStrategy() 方法:执行 metaObject.getValue(fieldName) 判断字段值是否为 null,再执行 Objects.nonNull(obj) 判断待设置的字段值是否为 null,然后执行 metaObject.setValue(fieldName, obj); 给字段设置值

/**
 * 元对象字段填充控制器抽象类,实现公共字段自动写入<p>
 * <p>
 * 所有入参的 MetaObject 必定是 entity 或其子类的 MetaObject
 *
 * @author hubin
 * @since 2016-08-28
 */
public interface MetaObjectHandler {

    /**
     * 是否开启了插入填充
     */
    default boolean openInsertFill() {
        return true;
    }

    /**
     * 是否开启了更新填充
     */
    default boolean openUpdateFill() {
        return true;
    }
    
    /**
     * @param metaObject metaObject meta object parameter
     * @return this
     * @since 3.3.0
     */
    default <T, E extends T> MetaObjectHandler strictInsertFill(MetaObject metaObject, String fieldName, Class<T> fieldType, E fieldVal) {
        return strictInsertFill(findTableInfo(metaObject), metaObject, Collections.singletonList(StrictFill.of(fieldName, fieldType, fieldVal)));
    }

    /**
     * @param metaObject metaObject meta object parameter
     * @return this
     * @since 3.3.0
     */
    default <T, E extends T> MetaObjectHandler strictInsertFill(MetaObject metaObject, String fieldName, Supplier<E> fieldVal, Class<T> fieldType) {
        return strictInsertFill(findTableInfo(metaObject), metaObject, Collections.singletonList(StrictFill.of(fieldName, fieldVal, fieldType)));
    }

    /**
     * @param metaObject metaObject meta object parameter
     * @return this
     * @since 3.3.0
     */
    default MetaObjectHandler strictInsertFill(TableInfo tableInfo, MetaObject metaObject, List<StrictFill<?, ?>> strictFills) {
        return strictFill(true, tableInfo, metaObject, strictFills);
    }

    /**
     * @param metaObject metaObject meta object parameter
     * @return this
     * @since 3.3.0
     */
    default <T, E extends T> MetaObjectHandler strictUpdateFill(MetaObject metaObject, String fieldName, Supplier<E> fieldVal, Class<T> fieldType) {
        return strictUpdateFill(findTableInfo(metaObject), metaObject, Collections.singletonList(StrictFill.of(fieldName, fieldVal, fieldType)));
    }

    /**
     * @param metaObject metaObject meta object parameter
     * @return this
     * @since 3.3.0
     */
    default <T, E extends T> MetaObjectHandler strictUpdateFill(MetaObject metaObject, String fieldName, Class<T> fieldType, E fieldVal) {
        return strictUpdateFill(findTableInfo(metaObject), metaObject, Collections.singletonList(StrictFill.of(fieldName, fieldType, fieldVal)));
    }

    /**
     * @param metaObject metaObject meta object parameter
     * @return this
     * @since 3.3.0
     */
    default MetaObjectHandler strictUpdateFill(TableInfo tableInfo, MetaObject metaObject, List<StrictFill<?, ?>> strictFills) {
        return strictFill(false, tableInfo, metaObject, strictFills);
    }

    /**
     * 严格填充,只针对非主键的字段,只有该表注解了fill 并且 字段名和字段属性 能匹配到才会进行填充(null 值不填充)
     *
     * @param insertFill  是否验证在 insert 时填充
     * @param tableInfo   cache 缓存
     * @param metaObject  metaObject meta object parameter
     * @param strictFills 填充信息
     * @return this
     * @since 3.3.0
     */
    default MetaObjectHandler strictFill(boolean insertFill, TableInfo tableInfo, MetaObject metaObject, List<StrictFill<?, ?>> strictFills) {
        if ((insertFill && tableInfo.isWithInsertFill()) || (!insertFill && tableInfo.isWithUpdateFill())) {
            strictFills.forEach(i -> {
                final String fieldName = i.getFieldName();
                final Class<?> fieldType = i.getFieldType();
                tableInfo.getFieldList().stream()
                    .filter(j -> j.getProperty().equals(fieldName) && fieldType.equals(j.getPropertyType()) &&
                        ((insertFill && j.isWithInsertFill()) || (!insertFill && j.isWithUpdateFill()))).findFirst()
                    .ifPresent(j -> strictFillStrategy(metaObject, fieldName, i.getFieldVal()));
            });
        }
        return this;
    }

    /**
     * 填充策略,默认有值不覆盖,如果提供的值为null也不填充
     *
     * @param metaObject metaObject meta object parameter
     * @param fieldName  java bean property name
     * @param fieldVal   java bean property value of Supplier
     * @return this
     * @since 3.3.0
     */
    default MetaObjectHandler fillStrategy(MetaObject metaObject, String fieldName, Object fieldVal) {
        if (getFieldValByName(fieldName, metaObject) == null) {
            setFieldValByName(fieldName, fieldVal, metaObject);
        }
        return this;
    }

    /**
     * 严格模式填充策略,默认有值不覆盖,如果提供的值为null也不填充
     *
     * @param metaObject metaObject meta object parameter
     * @param fieldName  java bean property name
     * @param fieldVal   java bean property value of Supplier
     * @return this
     * @since 3.3.0
     */
    default MetaObjectHandler strictFillStrategy(MetaObject metaObject, String fieldName, Supplier<?> fieldVal) {
        if (metaObject.getValue(fieldName) == null) {
            Object obj = fieldVal.get();
            if (Objects.nonNull(obj)) {
                metaObject.setValue(fieldName, obj);
            }
        }
        return this;
    }

StrictFill 类表示待填充的字段,类中记录了字段名、字段类型、获取字段值的函数

/**
 * 严格填充模式 model
 *
 * @author miemie
 * @since 2019-11-26
 */
@Data
@AllArgsConstructor
public class StrictFill<T, E extends T> {
    /**
     * 字段名
     */
    private String fieldName;
    /**
     * 字段类型
     */
    private Class<T> fieldType;
    /**
     * 获取字段值的函数
     */
    private Supplier<E> fieldVal;

    public static <T, E extends T> StrictFill<T, E> of(String fieldName, Class<T> fieldType, E fieldVal) {
        return new StrictFill<>(fieldName, fieldType, () -> fieldVal);
    }

    public static <T, E extends T> StrictFill<T, E> of(String fieldName, Supplier<E> fieldVal, Class<T> fieldType) {
        return new StrictFill<>(fieldName, fieldType, fieldVal);
    }
}

metaobject:元对象,是 Mybatis提供的一个用于更加方便,更加优雅的访问对象的属性,给对象的属性设置值 的一个对象,还用于包装对象,支持对 ObjectMapCollection 等对象进行包装。本质上 metaObject 获取对象的属性值或者是给对象的属性设置值,最终是要通过 Reflector 获取到属性的对应方法的 Invoker,最终执行 invoke

/**
 * @author Clinton Begin
 */
public class MetaObject {

  private final Object originalObject;
  private final ObjectWrapper objectWrapper;
  private final ObjectFactory objectFactory;
  private final ObjectWrapperFactory objectWrapperFactory;
  private final ReflectorFactory reflectorFactory;
    
  public String findProperty(String propName, boolean useCamelCaseMapping) {
    return objectWrapper.findProperty(propName, useCamelCaseMapping);
  }

  public String[] getGetterNames() {
    return objectWrapper.getGetterNames();
  }

  public String[] getSetterNames() {
    return objectWrapper.getSetterNames();
  }

  public Class<?> getSetterType(String name) {
    return objectWrapper.getSetterType(name);
  }

  public Class<?> getGetterType(String name) {
    return objectWrapper.getGetterType(name);
  }

  public boolean hasSetter(String name) {
    return objectWrapper.hasSetter(name);
  }

  public boolean hasGetter(String name) {
    return objectWrapper.hasGetter(name);
  }

  public Object getValue(String name) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
      MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
      if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
        return null;
      } else {
        return metaValue.getValue(prop.getChildren());
      }
    } else {
      return objectWrapper.get(prop);
    }
  }

  public void setValue(String name, Object value) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
      MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
      if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
        if (value == null) {
          // don't instantiate child path if value is null
          return;
        } else {
          metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
        }
      }
      metaValue.setValue(prop.getChildren(), value);
    } else {
      objectWrapper.set(prop, value);
    }
  }

6、执行 SQL 分析打印

使用 SQL 分析打印的步骤

引入 p6spy 的依赖

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

在 application.yml 配置文件中修改数据源配置

  1. 修改驱动类为 p6spy 提供的驱动类:driver-class-name: com.p6spy.engine.spy.P6SpyDriver
  2. 修改 url前的 连接协议:jdbc:p6spy:mysql

注意:该插件有性能损耗,不建议生产环境使用

# 数据源配置
spring:
  datasource:
    # driver-class-name: com.mysql.cj.jdbc.Driver
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver
    url: jdbc:p6spy:mysql://localhost:3307/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
    username: root
    password: root

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # SQL 日志配置
  global-config:
    db-config:
      logic-delete-field: flag  # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1     # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

logging:
  level:
    com.baomidou.mybatisplus.samples: debug

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
# 使用日志系统记录 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=com.mysql.cj.jdbc.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2

7、多数据源配置

TODO 以后有用到再来补笔记吧~
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值