第 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;@Versionprivate Integer version;@TableLogicprivate Integer deleted;}

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

在 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 {@Overridepublic 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,删除操作变为对逻辑字段的更新操作

3、SQL 注入器

SQL 注入器使用步骤

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

/*** 删除全部** @Author Oneby* @Date 2021/4/27 22:04*/
public class DeleteAll extends AbstractMethod {@Overridepublic 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 {@Overridepublic 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*/@Overridepublic 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* 里面包含自定义的全局方法*/@Beanpublic 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]
影响行数:6Creating 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);@Overridepublic 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 {@Overridepublic 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*/@Overridepublic 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;@Versionprivate Integer version;@TableLogicprivate 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 {@Overridepublic void insertFill(MetaObject metaObject) {log.info("start insert fill ....");this.strictInsertFill(metaObject, "createTime", Date.class, new Date()); // 起始版本 3.3.0(推荐使用)}@Overridepublic 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 nullreturn;} 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.Driverdriver-class-name: com.p6spy.engine.spy.P6SpyDriverurl: jdbc:p6spy:mysql://localhost:3307/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8username: rootpassword: rootmybatis-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 以后有用到再来补笔记吧~

第 8 章 MybatisPlus 扩展相关推荐

  1. 第一章 Joomla!扩展开发:概况

    第一章 Joomla!扩展开发:概况 你以前开发过动态网站但你的朋友告诉你有关Joomla!的事,所以你决定试一试.从食物网上那些出名的厨师中得到灵感后,你想建立一个简单的关于餐厅的网站.这个网站的安 ...

  2. 第 7 章 MybatisPlus 插件

    第 7 章 MybatisPlus 插件 1.插件机制概述 MybatisPlusInterceptor 核心插件 MybatisPlus 通过插件(Interceptor)可以做到拦截四大对象(Ex ...

  3. 第 6 章 MybatisPlus 代码生成器

    第 6 章 MybatisPlus 代码生成器 1.代码生成器简介 MybatisPlus AutoGenerator 代码生成器的简介 MybatisPlus 提供了强大的代码生成器,可以快速生成各 ...

  4. 第 5 章 MybatisPlus ActiveRecord

    第 5 章 MybatisPlus ActiveRecord 1.ActiveRecord 简介 ActiveRecord(活动记录) Active Record(活动记录 ),是一种领域模型模式,特 ...

  5. 第 4 章 MybatisPlus 条件构造器

    第 4 章 MybatisPlus 条件构造器 1.CRUD API 的思考 对于 BaseMapper 中基本 CRUD 方法的思考 继承了 BaseMapper 接口后,我们只能获得基本的 CRU ...

  6. 第 3 章 MybatisPlus 注入 SQL 原理分析

    第 3 章 MybatisPlus 注入 SQL 原理分析 思考问题 我们编写的 UserMapper 继承了 BaseMapper<T>,就拥有了基本的增删改查功能,这是因为 BaseM ...

  7. 第 2 章 MybatisPlus 通用 CRUD

    第 2 章 MybatisPlus 通用 CRUD 1.概述 回想一下,如果我们有一张 User 表,并且已经创建好了对应的实体类,实现 User 表的 CRUD 操作我们需要做什么呢? Mybati ...

  8. 第 1 章 MybatisPlus 快速入门

    第 1 章 MybatisPlus 快速入门 1.MybatisPlus 概述 MybatisPlus:一款 Mybatis 的增强工具包 MybatisPlus 官网:https://mybatis ...

  9. 第26章 FMC—扩展外部SDRAM—零死角玩转STM32-F429系列

    转载:http://blog.csdn.net/flyleaf91/article/details/52325516 第26章     FMC-扩展外部SDRAM 全套200集视频教程和1000页PD ...

最新文章

  1. 201521123111《Java程序设计》第2周学习总结
  2. 计组-中央处理器小结
  3. (算法)最长递增子序列
  4. 【ArcGIS微课1000例】0023:ArcGIS将地理照片(无人机照片)转为点(航迹)案例教程
  5. 今年不容易,要懂得爱护自己
  6. redhat/centos 搭建svn服务器环境
  7. 智汇上海:微软在中国的AI人工智能布局
  8. 施耐德PM5350电度表电能数据解析
  9. PHP 实现身份证号实名认证功能
  10. 贝叶斯网络大白话教程
  11. PYTHON-模块 re subprocess
  12. Java基础_17 | Java多线程程序设计(Java中两种创建线程的方法、多线程之间的同步和互斥)
  13. RoboMaster汇总
  14. 80后小学生必杀经典句子
  15. bat启动cmd,超级管理员
  16. DNF 关键组件Gameloader.exe
  17. Codeforces Round#539(Div. 2)
  18. gdc服务器sr1000自动化创建,GDC单机自动化操作
  19. 原假设“截距为0”双侧检验P值是多少_参数假设检验
  20. 安装mac版office

热门文章

  1. 历史上的今天:游戏机之父诞辰;搜索技术之父出生;MIT 公开演示旋风计算机...
  2. 中国开发者数量全球第二,C 语言一跌再跌 | GitHub 年度报告发布
  3. 程序员为教师妻子开发专属应用;2020 最佳开源项目出炉;中国构建全星地量子通信网|开发者周刊
  4. 稳居TIOBE前三,涨幅No.1,Python做了什么?
  5. 为什么我们没有选择Rust?
  6. JDK 15 正式发布,划时代的 ZGC 同时宣布转正!
  7. 中国数据库产业的“高地战事”
  8. 给力!一行代码躺赚普通程序员 10 年薪资!
  9. Python 玩出花了!一文教你用 Python 制作吃豆人游戏! | 附代码
  10. 马化腾、马云并列成为中国首富;百度回应“将上线电商直播”;.NET 5 Preview 2 发布 | 极客头条...