赞
踩
本文将结合源码介绍mybatis-plus的原理,包括:
系列文档:
MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。
mybatis-plus会自动为BaseMapper API创建MappedStatement,后续我们会分析这个过程:
public interface BaseMapper<T> extends Mapper<T> { int insert(T entity); int deleteById(Serializable id); int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap); int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); int updateById(@Param(Constants.ENTITY) T entity); int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper); T selectById(Serializable id); List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap); T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); <E extends IPage<T>> E selectPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); <E extends IPage<Map<String, Object>>> E selectMapsPage( E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); }
这个类也实现了FactoryBean接口,最终也会创建一个DefaultSqlSessionFactory对象,基本上和SqlSessionFactoryBean的作用一致,只是在中间加入了mybatis-plus的Statement注入等逻辑。
另外还有一些mybatis-plus的自定义配置参数,我们平时能够使用的只有GlobalConfig和DbConfig这两个类。
这个类由mybatis-plus提供,继承了mybatis的Configuration类,用于封装mybatis和mybatis-plus的核心配置参数。
与本文相关的内容暂时只有这个:
protected final MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this);
在分析mybatis getMapper(Class)原理时,我们了解到mybatis是使用MapperRegistry.getMapper(Class)方法实现的Mapper接口扫描和创建代理。而在mybatis-plus中,使用的是MybatisMapperRegistry类,这个类由mybatis-plus提供,继承了MapperRegistry类,重写了getMapper(Class)和addMapper(Class)等核心方法,在这些方法中实现了mybatis-plus BaseMapper API Statement的解析和注入。后续有详细分析。
封装全局配置信息:
public class GlobalConfig implements Serializable { // 是否开启LOGO private boolean banner = true; // 是否初始化SqlRunner private boolean enableSqlRunner = false; // 数据库相关配置 private DbConfig dbConfig; // SQL注入器 private ISqlInjector sqlInjector = new DefaultSqlInjector(); // Mapper父类 private Class<?> superMapperClass = Mapper.class; // 仅用于缓存SqlSessionFactory private SqlSessionFactory sqlSessionFactory; // 缓存已注入CRUD的Mapper信息 private Set<String> mapperRegistryCache = new ConcurrentSkipListSet<>(); // 元对象字段填充控制器 private MetaObjectHandler metaObjectHandler; // 主键生成器 private IdentifierGenerator identifierGenerator; }
封装DB通用配置:
public static class DbConfig { // 主键类型 private IdType idType = IdType.ASSIGN_ID; // 表名前缀 private String tablePrefix; // schema private String schema; // private String columnFormat; // private String propertyFormat; // private boolean replacePlaceholder; // 转义符 private String escapeSymbol; // 表名是否使用驼峰转下划线命名,只对表名生效 private boolean tableUnderline = true; // 大写命名,对表名和字段名均生效 private boolean capitalMode = false; // 表主键生成器 private IKeyGenerator keyGenerator; // 逻辑删除全局属性名 private String logicDeleteField; // 逻辑删除全局值,默认1表示已删除 private String logicDeleteValue = "1"; // 逻辑未删除全局值,默认0表示未删除 private String logicNotDeleteValue = "0"; // 字段insert验证策略 private FieldStrategy insertStrategy = FieldStrategy.NOT_NULL; // 字段update验证策略 private FieldStrategy updateStrategy = FieldStrategy.NOT_NULL; // 字段select验证策略 private FieldStrategy selectStrategy = FieldStrategy.NOT_NULL; }
这个类里面的insertStrategy、updateStrategy、selectStrategy后续深入分析时再做分析。
入口在**com.baomidou.mybatisplus.core.MybatisConfiguration.getMapper(Class, SqlSession)**方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mybatisMapperRegistry.getMapper(type, sqlSession);
}
这里的mybatisMapperRegistry就是上文介绍过的MybatisMapperRegistry类对象。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 这里换成MybatisMapperProxyFactory而不是MapperProxyFactory
final MybatisMapperProxyFactory<T> mapperProxyFactory = knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MybatisPlusMapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
public <T> void addMapper(Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { return; } boolean loadCompleted = false; try { // 这里也换成MybatisMapperProxyFactory而不是MapperProxyFactory knownMappers.put(type, new MybatisMapperProxyFactory<>(type)); // 这里也换成MybatisMapperAnnotationBuilder而不是MapperAnnotationBuilder MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type); // 这里有mybatis-plus的Statement解析、注入逻辑 parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
public void parse() { String resource = type.toString(); if (!configuration.isResourceLoaded(resource)) { loadXmlResource(); configuration.addLoadedResource(resource); String mapperName = type.getName(); assistant.setCurrentNamespace(mapperName); parseCache(); parseCacheRef(); // 从以下开始加入了mybatis-plus的核心逻辑 InterceptorIgnoreHelper.InterceptorIgnoreCache cache = InterceptorIgnoreHelper.initSqlParserInfoCache(type); for (Method method : type.getMethods()) { if (!canHaveStatement(method)) { continue; } if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent() && method.getAnnotation(ResultMap.class) == null) { parseResultMap(method); } try { // 加入注解过滤缓存 InterceptorIgnoreHelper.initSqlParserInfoCache(cache, mapperName, method); SqlParserHelper.initSqlParserInfoCache(mapperName, method); parseStatement(method); } catch (IncompleteElementException e) { // 使用MybatisMethodResolver而不是MethodResolver configuration.addIncompleteMethod(new MybatisMethodResolver(this, method)); } } // 注入CURD动态SQL,放在最后,可能会有人会用注解重写sql try { if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) { parserInjector(); } } catch (IncompleteElementException e) { configuration.addIncompleteMethod(new InjectorResolver(this)); } } parsePendingMethods(); }
由于mybatis-plus注入CRUD SQL的核心逻辑在这个方法,我们重点看一下这个方法:
void parserInjector() {
GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
}
这里获取到的是DefaultSqlInjector对象。前文分析过。
inspectInject方法:
public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) { // 解析model类型,比如Blog、User类型 Class<?> modelClass = extractModelClass(mapperClass); if (modelClass != null) { String className = mapperClass.toString(); Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration()); // 未解析过才做model解析 if (!mapperRegistryCache.contains(className)) { // 待解析的方法 List<AbstractMethod> methodList = this.getMethodList(mapperClass); if (CollectionUtils.isNotEmpty(methodList)) { // 通过model解析数据库表信息 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); } } } 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()); }
TableInfo类,封装从model解析出来的数据库表信息,这个类还是比较简单的:
public class TableInfo implements Constants { // 实体类型 private Class<?> entityType; // 表主键ID生成类型,不展开分析 private IdType idType = IdType.NONE; // 表名称 private String tableName; // 表映射结果集 private String resultMap; // 是否是需要自动生成的resultMap private boolean autoInitResultMap; // 主键是否有存在字段名与属性名关联,true表示要进行as private boolean keyRelated; // 表主键ID字段名 private String keyColumn; // 表主键ID属性名 private String keyProperty; // 表主键ID属性类型 private Class<?> keyType; // 表主键ID Sequence private KeySequence keySequence; // 表字段信息列表 private List<TableFieldInfo> fieldList; // 命名空间,对应的mapper接口的全类名 private String currentNamespace; // MybatisConfiguration引用 private Configuration configuration; // 是否开启下划线转驼峰 private boolean underCamel; // 缓存包含主键及字段的sql select private String allSqlSelect; // 缓存主键字段的sql select private String sqlSelect; // 表字段是否启用了插入填充 private boolean withInsertFill; // 表字段是否启用了更新填充 private boolean withUpdateFill; // 表字段是否启用了逻辑删除 private boolean withLogicDelete; // 逻辑删除字段 private TableFieldInfo logicDeleteFieldInfo; // 表字段是否启用了乐观锁 private boolean withVersion; // 乐观锁字段 private TableFieldInfo versionFieldInfo;
TableFieldInfo类,封装表字段信息:
public class TableFieldInfo implements Constants { // 属性的引用 private final Field field; // 字段名 private final String column; // 属性名 private final String property; // 属性表达式#{property}, 可以指定jdbcType, typeHandler等 private final String el; // 属性类型 private final Class<?> propertyType; // 是否是基本数据类型 private final boolean isPrimitive; // 属性是否是CharSequence类型 private final boolean isCharSequence; // 字段insert验证策略 private final FieldStrategy insertStrategy; // 字段update验证策略 private final FieldStrategy updateStrategy; // 字段where验证策略 private final FieldStrategy whereStrategy; // 是否是乐观锁字段 private final boolean version; // 是否进行select查询 private boolean select = true; // 是否是逻辑删除字段 private boolean logicDelete = false; // 逻辑删除值 private String logicDeleteValue; // 逻辑未删除值 private String logicNotDeleteValue; // 字段update set部分注入 private String update; // where字段比较条件 private String condition = SqlCondition.EQUAL; // 字段填充策略 private FieldFill fieldFill = FieldFill.DEFAULT; // 表字段是否启用了插入填充 private boolean withInsertFill; // 表字段是否启用了更新填充 private boolean withUpdateFill; // 缓存sql select private String sqlSelect; // JDBC类型 private JdbcType jdbcType; // 类型处理器 private Class<? extends TypeHandler<?>> typeHandler;
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);
}
// 这是一个抽象方法,由子类实现,以SelectList类为例
public abstract MappedStatement injectMappedStatement(
Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo);
SelectList类:
public class SelectList extends AbstractMethod { @Override public MappedStatement injectMappedStatement( Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { // <script>%s SELECT %s FROM %s %s %s</script> SqlMethod sqlMethod = SqlMethod.SELECT_LIST; // 这里根据sqlMethod、tableInfo等信息生成一个动态SQL // 与我们在mapper xml文件里面配置效果一样 String sql = String.format( sqlMethod.getSql(), sqlFirst(), sqlSelectColumns(tableInfo, true), tableInfo.getTableName(), sqlWhereEntityWrapper(true, tableInfo), sqlComment()); // 生成SqlSource,此处是一个动态DynamicSqlSource对象 // 此处languageDriver是MybatisXMLLanguageDriver对象 SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); return this.addSelectMappedStatementForTable(mapperClass, getMethod(sqlMethod), sqlSource, tableInfo); } } // MybatisXMLLanguageDriver.createSqlSource(configuration, sql, modelClass) public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) { GlobalConfig.DbConfig config = GlobalConfigUtils.getDbConfig(configuration); if (config.isReplacePlaceholder()) { List<String> find = SqlUtils.findPlaceholder(script); if (CollectionUtils.isNotEmpty(find)) { try { script = SqlUtils.replaceSqlPlaceholder(script, find, config.getEscapeSymbol()); } catch (MybatisPlusException e) { throw new IncompleteElementException(); } } } return super.createSqlSource(configuration, script, parameterType); } // super.createSqlSource(configuration, script, parameterType) // 这里回到了之前分析mapper xml解析的代码上,已经分析过,此处不再展开分析 public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) { XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType); return builder.parseScriptNode(); } public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) { if (script.startsWith("<script>")) { XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver()); return createSqlSource(configuration, parser.evalNode("/script"), parameterType); } else { script = PropertyParser.parse(script, configuration.getVariables()); TextSqlNode textSqlNode = new TextSqlNode(script); if (textSqlNode.isDynamic()) { return new DynamicSqlSource(configuration, textSqlNode); } else { return new RawSqlSource(configuration, script, parameterType); } } }
this.addSelectMappedStatementForTable(mapperClass, getMethod(sqlMethod), sqlSource, tableInfo)就是创建MappedStatement对象并将其注册到Configuration中:
protected MappedStatement addSelectMappedStatementForTable( Class<?> mapperClass, String id, SqlSource sqlSource, TableInfo table) { String resultMap = table.getResultMap(); if (null != resultMap) { /* 返回 resultMap 映射结果集 */ return addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.SELECT, null, resultMap, null, new NoKeyGenerator(), null, null); } else { /* 普通查询 */ return addSelectMappedStatementForOther(mapperClass, id, sqlSource, table.getEntityType()); } } protected MappedStatement addMappedStatement( Class<?> mapperClass, String id, SqlSource sqlSource, SqlCommandType sqlCommandType, Class<?> parameterType, String resultMap, Class<?> resultType, KeyGenerator keyGenerator, String keyProperty, String keyColumn) { String statementName = mapperClass.getName() + DOT + id; if (hasMappedStatement(statementName)) { return null; } /* 缓存逻辑处理 */ boolean isSelect = false; if (sqlCommandType == SqlCommandType.SELECT) { isSelect = true; } // 这里就回到了之前分析过的注册MappedStatement的逻辑,不再展开分析 return builderAssistant.addMappedStatement(id, sqlSource, StatementType.PREPARED, sqlCommandType, null, null, null, parameterType, resultMap, resultType, null, !isSelect, isSelect, false, keyGenerator, keyProperty, keyColumn, configuration.getDatabaseId(), languageDriver, null); }
到这里为止,mybatis-plus注册BaseMapper API Statement的逻辑就分析完成了。
List<Blog> list = this.blogMapper.selectList(new LambdaQueryWrapper<Blog>()
.like(Blog::getTitle, "spring")
.eq(Blog::getId, 2)
);
BaseMapper接口的大多数方法都接收一个Wrapper的实现类对象作为查询条件。
比如:
T selectOne(Wrapper<T> queryWrapper);
Integer selectCount(Wrapper<T> queryWrapper);
List<T> selectList(Wrapper<T> queryWrapper);
List<Map<String, Object>> selectMaps(Wrapper<T> queryWrapper);
List<Object> selectObjs(Wrapper<T> queryWrapper);
这个类实现了ISqlSegment接口,自己也定义了一些方法,这些方法在之后拼接动态SQL时会起到很大的作用:
public abstract class Wrapper<T> implements ISqlSegment { public abstract T getEntity(); public String getSqlSelect(); public String getSqlSet(); public String getSqlComment(); public String getSqlFirst(); // 获取MergeSegments public abstract MergeSegments getExpression(); /** * 获取自定义SQL 简化自定义XML复杂情况 * 使用方法: `select xxx from table` + ${ew.customSqlSegment} */ public String getCustomSqlSegment(); // 查询条件为空(包含entity) public boolean isEmptyOfWhere(); // 查询条件不为空(包含entity) public boolean nonEmptyOfWhere(); // 查询条件为空(不包含entity) public boolean isEmptyOfNormal(); // 查询条件为空(不包含entity) public boolean nonEmptyOfNormal(); // 深层实体判断属性 public boolean nonEmptyOfEntity(); // 根据实体FieldStrategy属性来决定判断逻辑 private boolean fieldStrategyMatch(T entity, TableFieldInfo e); // 深层实体判断属性 public boolean isEmptyOfEntity(); // 获取格式化后的执行sql public String getTargetSql(); // 条件清空 abstract public void clear(); } /** * SQL片段接口 */ @FunctionalInterface public interface ISqlSegment extends Serializable { // 获取SQL片段 String getSqlSegment(); }
他有一个抽象子类:AbstractWrapper类。
定义了核心的条件拼接方法,比如:
public Children eq(boolean condition, R column, Object val); public Children ne(boolean condition, R column, Object val); public Children gt(boolean condition, R column, Object val); public Children ge(boolean condition, R column, Object val); public Children lt(boolean condition, R column, Object val); public Children le(boolean condition, R column, Object val); public Children like(boolean condition, R column, Object val); public Children notLike(boolean condition, R column, Object val); public Children likeLeft(boolean condition, R column, Object val); public Children likeRight(boolean condition, R column, Object val); public Children between(boolean condition, R column, Object val1, Object val2); public Children notBetween(boolean condition, R column, Object val1, Object val2); public Children and(boolean condition, Consumer<Children> consumer); public Children or(boolean condition, Consumer<Children> consumer); public Children exists(boolean condition, String existsSql); public Children notExists(boolean condition, String existsSql); public Children isNull(boolean condition, R column); public Children isNotNull(boolean condition, R column); public Children in(boolean condition, R column, Collection<?> coll); public Children notIn(boolean condition, R column, Collection<?> coll); public Children groupBy(boolean condition, R... columns); public Children orderBy(boolean condition, boolean isAsc, R... columns); public Children having(boolean condition, String sqlHaving, Object... params);
方法的实现此处不做展开。
这个类继承了AbstractWrapper抽象类,实现了Query接口,实现了Query中的方法:
public QueryWrapper<T> select(String... columns);
public QueryWrapper<T> select(Class entityClass, Predicate predicate);
public String getSqlSelect();
protected QueryWrapper<T> instance();
public void clear();
AbstractLambdaWrapper类重写了AbstractWrapper类的columnsToString方法,使用lambda转换列名。
此处的lambda解析还是很值得学习的,暂时不展开分析了。
首先看一下MappedStatement的结构,之后再做分析:
上面的四张图简单说明一下:
在介绍完LambdaQueryWrapper之后我们在详细说明这里面的几个重要属性。
default Children like(R column, Object val) { return like(true, column, val); } // AbstractWrapper.like public Children like(boolean condition, R column, Object val) { return likeValue(condition, LIKE, column, val, SqlLike.DEFAULT); } protected Children likeValue( boolean condition, SqlKeyword keyword, R column, Object val, SqlLike sqlLike) { return doIt( condition, () -> columnToString(column), // 列名 keyword, // LIKE // #{ew.paramNameValuePairs.MPGENVAL1} // 生成类似这样的表达式 () -> formatSql("{0}", SqlUtils.concatLike(val, sqlLike))); } protected Children doIt(boolean condition, ISqlSegment... sqlSegments) { if (condition) { // 此处将sqlSegments加到了expression中,这是一个容器,保存所有的ISqlSegment expression.add(sqlSegments); } return typedThis; } // protected MergeSegments expression;
MergeSegments类,从名字可以看出,这个类聚合了其他类型的ISqlSegment对象:
public class MergeSegments implements ISqlSegment { private final NormalSegmentList normal = new NormalSegmentList(); private final GroupBySegmentList groupBy = new GroupBySegmentList(); private final HavingSegmentList having = new HavingSegmentList(); private final OrderBySegmentList orderBy = new OrderBySegmentList(); private String sqlSegment = StringPool.EMPTY; private boolean cacheSqlSegment = true; public void add(ISqlSegment... iSqlSegments) { List<ISqlSegment> list = Arrays.asList(iSqlSegments); ISqlSegment firstSqlSegment = list.get(0); if (MatchSegment.ORDER_BY.match(firstSqlSegment)) { orderBy.addAll(list); } else if (MatchSegment.GROUP_BY.match(firstSqlSegment)) { groupBy.addAll(list); } else if (MatchSegment.HAVING.match(firstSqlSegment)) { having.addAll(list); } else { normal.addAll(list); } cacheSqlSegment = false; } // 这个方法后续再详细分析 @Override public String getSqlSegment() { if (cacheSqlSegment) { return sqlSegment; } cacheSqlSegment = true; if (normal.isEmpty()) { if (!groupBy.isEmpty() || !orderBy.isEmpty()) { sqlSegment = groupBy.getSqlSegment() + having.getSqlSegment() + orderBy.getSqlSegment(); } } else { sqlSegment = normal.getSqlSegment() + groupBy.getSqlSegment() + having.getSqlSegment() + orderBy.getSqlSegment(); } return sqlSegment; } public void clear() { // ... } }
与like方法一样,不展开分析了。
public class SelectList extends AbstractMethod { @Override public MappedStatement injectMappedStatement( Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { // 封装原始SQL SqlMethod sqlMethod = SqlMethod.SELECT_LIST; // 格式化动态SQL String sql = String.format( sqlMethod.getSql(), sqlFirst(), sqlSelectColumns(tableInfo, true), tableInfo.getTableName(), sqlWhereEntityWrapper(true, tableInfo), sqlComment()); // 生成DynamicSqlSource SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); return this.addSelectMappedStatementForTable(mapperClass, getMethod(sqlMethod), sqlSource, tableInfo); } }
原始SQL:
<script>%s SELECT %s FROM %s %s %s\\n</script>
格式化后的动态SQL:
<script> <choose> <when test="ew != null and ew.sqlFirst != null"> ${ew.sqlFirst} </when> <otherwise></otherwise> </choose> SELECT <!-- 拼接查询字段 --> <choose> <when test="ew != null and ew.sqlSelect != null"> ${ew.sqlSelect} </when> <otherwise>id,title,content,create_time,update_time</otherwise> </choose> FROM blog <!-- 拼接表名 --> <!-- 拼接查询条件 --> <if test="ew != null"> <where> <!-- 拼接实体对象查询条件 --> <if test="ew.entity != null"> <if test="ew.entity.id != null">id=#{ew.entity.id}</if> <if test="ew.entity['title'] != null"> AND title=#{ew.entity.title}</if> <if test="ew.entity['content'] != null"> AND content=#{ew.entity.content}</if> <if test="ew.entity['createTime'] != null"> AND create_time=#{ew.entity.createTime}</if> <if test="ew.entity['updateTime'] != null"> AND update_time=#{ew.entity.updateTime}</if> </if> <!-- 拼接Wrapper对象查询条件 --> <if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.nonEmptyOfWhere"> <if test="ew.nonEmptyOfEntity and ew.nonEmptyOfNormal"> AND</if> ${ew.sqlSegment} </if> </where> <!-- 拼接Wrapper对象查询条件,这里通常没有办法进来,存在疑问 --> <if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.emptyOfWhere"> ${ew.sqlSegment} </if> </if> <choose> <when test="ew != null and ew.sqlComment != null"> ${ew.sqlComment} </when> <otherwise></otherwise> </choose> </script>
生成的SqlSource:
之前分析过了,此处不再展开。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。