mybatis-plus 属性为空时判断问题

最近在做项目时,发现前端调用查询接口,发现接口返回数据不对。我通过日志发现前端查询字段值为空时,竟然被当作一个条件,因为后端采用的mybatis-plus,通过官网我找到了一个配置

mybatis-plus:

global-config:

db-config:

select-strategy: not_empty

然后再测试一遍发现好使了。

我决定看一下mybatis-plus的底层时怎么实现的。

mybatis-plus 为我们提供了许多默认的方法,通过继承BaseMapper就可以实现,无需配置xml,具体的方法可以参考mybatis-plus的官方网站:

mybatis-plus

mybatis 在启动的时候,会根据mapper方法,生成一个MappedStatement,一个mapper的方法会对应一个MappedStatement.

public final class MappedStatement {

private String resource;

private Configuration configuration;

private String id;

private Integer fetchSize;

private Integer timeout;

private StatementType statementType;

private ResultSetType resultSetType;

private SqlSource sqlSource;

private Cache cache;

private ParameterMap parameterMap;

private ListresultMaps;

private boolean flushCacheRequired;

private boolean useCache;

private boolean resultOrdered;

private SqlCommandType sqlCommandType;

private KeyGenerator keyGenerator;

private String[] keyProperties;

private String[] keyColumns;

private boolean hasNestedResultMaps;

private String databaseId;

private Log statementLog;

private LanguageDriver lang;

private String[] resultSets;

由此可以想到,mybatis-plus 启动的时候会生成一些默认的方法的 MappedStatement我们先看MybatisConfiguration 的addMapper 方法

@Override

public void addMapper(Classtype) {

mybatisMapperRegistry.addMapper(type);

}

跳转到MybatisMapperRegistry 类中

@Override

public void addMapper(Classtype) {

if (type.isInterface()) {

if (hasMapper(type)) {

// TODO 如果之前注入 直接返回

return;

// TODO 这里就不抛异常了

// throw new BindingException("Type " + type + " is already known to the MapperRegistry.");

}

boolean loadCompleted = false;

try {

// TODO 这里也换成 MybatisMapperProxyFactory 而不是 MapperProxyFactory

knownMappers.put(type, new MybatisMapperProxyFactory<>(type));

// It's important that the type is added before the parser is run

// otherwise the binding may automatically be attempted by the

// mapper parser. If the type is already known, it won't try.

// TODO 这里也换成 MybatisMapperAnnotationBuilder 而不是 MapperAnnotationBuilder

MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);

parser.parse();

loadCompleted = true;

} finally {

if (!loadCompleted) {

knownMappers.remove(type);

}

}

}

}

重点看 parser.parse() 我们在点进去看

@Override

public void parse() {

String resource = type.toString();

if (!configuration.isResourceLoaded(resource)) {

loadXmlResource();

configuration.addLoadedResource(resource);

final String typeName = type.getName();

assistant.setCurrentNamespace(typeName);

parseCache();

parseCacheRef();

SqlParserHelper.initSqlParserInfoCache(type);

Method[] methods = type.getMethods();

for (Method method : methods) {

try {

// issue #237

if (!method.isBridge()) {

parseStatement(method);

SqlParserHelper.initSqlParserInfoCache(typeName, method);

}

} catch (IncompleteElementException e) {

// TODO 使用 MybatisMethodResolver 而不是 MethodResolver

configuration.addIncompleteMethod(new MybatisMethodResolver(this, method));

}

}

// TODO 注入 CURD 动态 SQL , 放在在最后, because 可能会有人会用注解重写sql

if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {

GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);

}

}

parsePendingMethods();

}

我们看inspectInject方法:

@Override

public void inspectInject(MapperBuilderAssistant builderAssistant, Class> mapperClass) {

Class> modelClass = extractModelClass(mapperClass);

if (modelClass != null) {

String className = mapperClass.toString();

SetmapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());

if (!mapperRegistryCache.contains(className)) {

ListmethodList = 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);

}

}

}

然后再看 m.inject方法:

/**

* 注入自定义方法

*/

public void inject(MapperBuilderAssistant builderAssistant, Class> mapperClass, Class> modelClass, TableInfo tableInfo) {

this.configuration = builderAssistant.getConfiguration();

this.builderAssistant = builderAssistant;

this.languageDriver = configuration.getDefaultScriptingLanguageInstance();

/* 注入自定义方法 */

injectMappedStatement(mapperClass, modelClass, tableInfo);

}

终于看到增加MappedStatement了,injectMappedStatement有很多实现类

因为是查询 我们看selectList

@Override

public MappedStatement injectMappedStatement(Class> mapperClass, Class> modelClass, TableInfo tableInfo) {

SqlMethod sqlMethod = SqlMethod.SELECT_LIST;

String sql = String.format(sqlMethod.getSql(), sqlSelectColumns(tableInfo, true),

tableInfo.getTableName(), sqlWhereEntityWrapper(true, tableInfo),

sqlComment());

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

return this.addSelectMappedStatementForTable(mapperClass, sqlMethod.getMethod(), sqlSource, tableInfo);

}

因为是查询条件,我们看sqlWhereEntityWrapper 方法 此方法会封装一些sql的条件语句:,最后来到TableFieldInfo的getSqlWhere方法:

public String getSqlWhere(final String prefix) {

final String newPrefix = prefix == null ? EMPTY : prefix;

// 默认: AND column=#{prefix + el}

String sqlScript = " AND " + String.format(condition, column, newPrefix + el);

// 查询的时候只判非空

return convertIf(sqlScript, newPrefix + property, whereStrategy);

}

然后进入convertIf方法:

private String convertIf(final String sqlScript, final String property, final FieldStrategy fieldStrategy) {

if (fieldStrategy == FieldStrategy.NEVER) {

return null;

}

if (fieldStrategy == FieldStrategy.IGNORED) {

return sqlScript;

}

if (fieldStrategy == FieldStrategy.NOT_EMPTY && isCharSequence) {

return SqlScriptUtils.convertIf(sqlScript, String.format("%s != null and %s != ''", property, property),

false);

}

return SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", property), false);

}

通过代码可以看到生成的sql条件对空判断和策略有关,那我们就去找TableFieldInfo创建的时候是怎么赋值的,我们回到AbstractSqlInjector中

@Override

public void inspectInject(MapperBuilderAssistant builderAssistant, Class> mapperClass) {

Class> modelClass = extractModelClass(mapperClass);

if (modelClass != null) {

String className = mapperClass.toString();

SetmapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());

if (!mapperRegistryCache.contains(className)) {

ListmethodList = 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);

}

}

}

TableFieldInfo 是TableInfo的属性,我们看initTableInfo方法:

/* 初始化字段相关 */

initTableFields(clazz, globalConfig, tableInfo);

此方法就是创建TableFieldInfo:

/* 无 @TableField 注解的字段初始化 */

fieldList.add(new TableFieldInfo(dbConfig, tableInfo, field));

然后看TableFieldInfo的构造方法:

public TableFieldInfo(GlobalConfig.DbConfig dbConfig, TableInfo tableInfo, Field field) {

this.version = field.getAnnotation(Version.class) != null;

this.property = field.getName();

this.propertyType = field.getType();

this.isCharSequence = StringUtils.isCharSequence(this.propertyType);

this.el = this.property;

this.insertStrategy = dbConfig.getInsertStrategy();

this.updateStrategy = dbConfig.getUpdateStrategy();

this.whereStrategy = dbConfig.getSelectStrategy();

tableInfo.setLogicDelete(this.initLogicDelete(dbConfig, field));

可以看到this.whereStrategy = dbConfig.getSelectStrategy();

就是说这个策略可以配置来改变

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐