第 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;@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
全局配置类的 logicDeleteField
、logicDeleteValue
、logicNotDeleteValue
字段中
/*** 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 语句的逻辑
- 如果
if (tableInfo.isWithLogicDelete())
成立,即表中有逻辑删除字段,MybatisPlus 会将删除操作变为更新操作:addUpdateMappedStatement(mapperClass, modelClass, getMethod(sqlMethod), sqlSource)
- 否则
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 注入器使用步骤
编写自定义注入方法:创建 DeleteAll
、MysqlInsertAllBatch
类,继承自 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_user
;mysqlInsertAllBatch()
方法插入的字段为: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)
自动填充功能的原理分析
- 填充原理是直接给
entity
的属性设置值!!! - 注解则是指定该属性在对应情况下必有值,如果无值则入库会是
null
MetaObjectHandler
提供的默认方法的策略均为:如果属性有值则不覆盖,如果填充值为null
则不填充- 字段必须声明
TableField
注解,属性fill
选择对应策略,该声明告知Mybatis-Plus
需要预留注入SQL
字段 - 填充处理器
MyMetaObjectHandler
在 Spring Boot 中需要声明@Component
或@Bean
注入 - 要想根据注解
FieldFill.xxx
和字段名
以及字段类型
来区分必须使用父类的strictInsertFill
或者strictUpdateFill
方法 - 不需要根据任何来区分可以使用父类的
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提供的一个用于更加方便,更加优雅的访问对象的属性,给对象的属性设置值 的一个对象,还用于包装对象,支持对 Object
、Map
、Collection
等对象进行包装。本质上 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 配置文件中修改数据源配置
- 修改驱动类为
p6spy
提供的驱动类:driver-class-name: com.p6spy.engine.spy.P6SpyDriver
- 修改
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 扩展相关推荐
- 第一章 Joomla!扩展开发:概况
第一章 Joomla!扩展开发:概况 你以前开发过动态网站但你的朋友告诉你有关Joomla!的事,所以你决定试一试.从食物网上那些出名的厨师中得到灵感后,你想建立一个简单的关于餐厅的网站.这个网站的安 ...
- 第 7 章 MybatisPlus 插件
第 7 章 MybatisPlus 插件 1.插件机制概述 MybatisPlusInterceptor 核心插件 MybatisPlus 通过插件(Interceptor)可以做到拦截四大对象(Ex ...
- 第 6 章 MybatisPlus 代码生成器
第 6 章 MybatisPlus 代码生成器 1.代码生成器简介 MybatisPlus AutoGenerator 代码生成器的简介 MybatisPlus 提供了强大的代码生成器,可以快速生成各 ...
- 第 5 章 MybatisPlus ActiveRecord
第 5 章 MybatisPlus ActiveRecord 1.ActiveRecord 简介 ActiveRecord(活动记录) Active Record(活动记录 ),是一种领域模型模式,特 ...
- 第 4 章 MybatisPlus 条件构造器
第 4 章 MybatisPlus 条件构造器 1.CRUD API 的思考 对于 BaseMapper 中基本 CRUD 方法的思考 继承了 BaseMapper 接口后,我们只能获得基本的 CRU ...
- 第 3 章 MybatisPlus 注入 SQL 原理分析
第 3 章 MybatisPlus 注入 SQL 原理分析 思考问题 我们编写的 UserMapper 继承了 BaseMapper<T>,就拥有了基本的增删改查功能,这是因为 BaseM ...
- 第 2 章 MybatisPlus 通用 CRUD
第 2 章 MybatisPlus 通用 CRUD 1.概述 回想一下,如果我们有一张 User 表,并且已经创建好了对应的实体类,实现 User 表的 CRUD 操作我们需要做什么呢? Mybati ...
- 第 1 章 MybatisPlus 快速入门
第 1 章 MybatisPlus 快速入门 1.MybatisPlus 概述 MybatisPlus:一款 Mybatis 的增强工具包 MybatisPlus 官网:https://mybatis ...
- 第26章 FMC—扩展外部SDRAM—零死角玩转STM32-F429系列
转载:http://blog.csdn.net/flyleaf91/article/details/52325516 第26章 FMC-扩展外部SDRAM 全套200集视频教程和1000页PD ...
最新文章
- 201521123111《Java程序设计》第2周学习总结
- 计组-中央处理器小结
- (算法)最长递增子序列
- 【ArcGIS微课1000例】0023:ArcGIS将地理照片(无人机照片)转为点(航迹)案例教程
- 今年不容易,要懂得爱护自己
- redhat/centos 搭建svn服务器环境
- 智汇上海:微软在中国的AI人工智能布局
- 施耐德PM5350电度表电能数据解析
- PHP 实现身份证号实名认证功能
- 贝叶斯网络大白话教程
- PYTHON-模块 re subprocess
- Java基础_17 | Java多线程程序设计(Java中两种创建线程的方法、多线程之间的同步和互斥)
- RoboMaster汇总
- 80后小学生必杀经典句子
- bat启动cmd,超级管理员
- DNF 关键组件Gameloader.exe
- Codeforces Round#539(Div. 2)
- gdc服务器sr1000自动化创建,GDC单机自动化操作
- 原假设“截距为0”双侧检验P值是多少_参数假设检验
- 安装mac版office
热门文章
- 历史上的今天:游戏机之父诞辰;搜索技术之父出生;MIT 公开演示旋风计算机...
- 中国开发者数量全球第二,C 语言一跌再跌 | GitHub 年度报告发布
- 程序员为教师妻子开发专属应用;2020 最佳开源项目出炉;中国构建全星地量子通信网|开发者周刊
- 稳居TIOBE前三,涨幅No.1,Python做了什么?
- 为什么我们没有选择Rust?
- JDK 15 正式发布,划时代的 ZGC 同时宣布转正!
- 中国数据库产业的“高地战事”
- 给力!一行代码躺赚普通程序员 10 年薪资!
- Python 玩出花了!一文教你用 Python 制作吃豆人游戏! | 附代码
- 马化腾、马云并列成为中国首富;百度回应“将上线电商直播”;.NET 5 Preview 2 发布 | 极客头条...