We know that in mybatis, we can perform nested queries for a column of values as input parameters, so what should we do if there are multiple input parameters? Mybatis supports compound mapping. Let's look at the use of compound mapping through example code.
<resultMap id="postLiteMap2NestedWithSelect" type="org.apache.ibatis.domain.blog.BlogLite"> <id column="blog_id" property="id" /> <collection property="posts" ofType="org.apache.ibatis.domain.blog.PostLite"> <constructor> <arg javaType="org.apache.ibatis.domain.blog.PostLiteId" column="{id=id}" select="selectPostLiteId" /> <arg javaType="_int" column="blog_id"/> </constructor> </collection> </resultMap> <mapper namespace="org.apache.ibatis.domain.blog.mappers.PostMapper"> <resultMap id="postLiteIdMap" type="org.apache.ibatis.domain.blog.PostLiteId"> <constructor> <idArg javaType="_int" column="id"/> </constructor> </resultMap> <select id="selectPostLite2NestedWithSelect" resultMap="postLiteMap2NestedWithSelect"> select id, 1 as blog_id from post where blog_id is not null </select> <select id="selectPostLiteId" resultMap="postLiteIdMap"> select ${id} as id from (values(0)) as t </select>
Inquire
List<BlogLite> posts = session.selectList("org.apache.ibatis.domain.blog.mappers.PostMapper.selectPostLite2NestedWithSelect");
What kind of processing is this? Let's take a look at the sequence diagram
Let's see how Mybatis handles it through the code
public ResultMapping buildResultMapping( Class<?> resultType, String property, String column, Class<?> javaType, JdbcType jdbcType, String nestedSelect, String nestedResultMap, String notNullColumn, String columnPrefix, Class<? extends TypeHandler<?>> typeHandler, List<ResultFlag> flags, String resultSet, String foreignColumn, boolean lazy) { // Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType); //type handler TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler); // Parse mixed columns List<ResultMapping> composites = parseCompositeColumnName(column); //Build ResultMapping return new ResultMapping.Builder(configuration, property, column, javaTypeClass) .jdbcType(jdbcType) // Namespace processing of nested query IDs .nestedQueryId(applyCurrentNamespace(nestedSelect, true)) //Namespace processing of nested ResultMap .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true)) .resultSet(resultSet) .typeHandler (typeHandlerInstance) .flags(flags == null ? new ArrayList<ResultFlag>() : flags) .composites(composites) .notNullColumns(parseMultipleColumnNames(notNullColumn)) .columnPrefix(columnPrefix) .foreignColumn(foreignColumn) .lazy(lazy) .build(); } private List<ResultMapping> parseCompositeColumnName(String columnName) { List<ResultMapping> composites = new ArrayList<ResultMapping>(); //If columnName is not null and columnName contains "=" or contains "," if (columnName != null && (columnName.indexOf('=') > -1 || columnName.indexOf(',') > -1)) { // split the string StringTokenizer parser = new StringTokenizer(columnName, "{}=, ", false); while (parser.hasMoreTokens()) { //get properties String property = parser.nextToken(); //get column String column = parser.nextToken(); //Build a composite ResultMapping ResultMapping complexResultMapping = new ResultMapping.Builder( configuration, property, column, configuration.getTypeHandlerRegistry().getUnknownTypeHandler()).build(); composites.add(complexResultMapping); } } return composites; }
The result of this is:
So far, it can be found that {id=id} is parsed into a composite resultMapping, so how is it handled when it is used?
In DefaultResultSetHandler, the nested query processing in the constructor is as follows, if the configuration is a composite mapping, the internal mapping of the processing composite mapping
//Get the value of the nested query constructor parameter private Object getNestedQueryConstructorValue(ResultSet rs, ResultMapping constructorMapping, String columnPrefix) throws SQLException { // Nested query ID final String nestedQueryId = constructorMapping.getNestedQueryId(); //Nested query MappedStatement final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId); // Nested query parameter types final Class nestedQueryParameterType = nestedQuery.getParameterMap().getType(); //Get the nested query input parameter value final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, constructorMapping, nestedQueryParameterType, columnPrefix); Object value = null; if (nestedQueryParameterObject != null) { final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject); final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql); final Class targetType = constructorMapping.getJavaType(); final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql); value = resultLoader.loadResult(); } return value; } private Object prepareParameterForNestedQuery(ResultSet rs, ResultMapping resultMapping, Class parameterType, String columnPrefix) throws SQLException { //if it is a composite map if (resultMapping.isCompositeResult()) { return prepareCompositeKeyParameter(rs, resultMapping, parameterType, columnPrefix); } else { return prepareSimpleKeyParameter(rs, resultMapping, parameterType, columnPrefix); } } private Object prepareCompositeKeyParameter(ResultSet rs, ResultMapping resultMapping, Class parameterType, String columnPrefix) throws SQLException { //create parameter object final Object parameterObject = instantiateParameterObject(parameterType); final MetaObject metaObject = configuration.newMetaObject(parameterObject); boolean foundValues = false; // Traverse the composite result map for (ResultMapping innerResultMapping : resultMapping.getComposites()) { //get parameter type final Class propType = metaObject.getSetterType(innerResultMapping.getProperty()); //get type handler final TypeHandler typeHandler = typeHandlerRegistry.getTypeHandler(propType); //get value final Object propValue = typeHandler.getResult(rs, prependPrefix(innerResultMapping.getColumn(), columnPrefix)); // Set to the parameter object if the parameter value is not null if (propValue != null) { metaObject.setValue(innerResultMapping.getProperty(), propValue); foundValues = true; } } return foundValues ? parameterObject : null; }
The input parameter id value obtained at this time is 1, and compound mapping can also be used in other nested queries.
<resultMap id="addressMapper" type="org.apache.ibatis.submitted.column_prefix.Address"> <constructor> <idArg column="id" javaType="int" /> <arg column="state" javaType="string" /> </constructor> <result property="city" column="city" /> <result property="hasPhone" column="has_phone" /> <association property="stateBird" select="selectStateBird" column="state" /> <association property="zip" select="selectZip" column="{state=state,city=city}" /> <association property="phone1" select="selectPhone" column="phone1_id" /> <association property="phone2" select="selectPhone" column="phone2_id" /> <discriminator column="addr_type" javaType="int"> <case value="1" resultType="org.apache.ibatis.submitted.column_prefix.AddressWithCaution"> <result property="caution" column="caution" /> </case> </discriminator> </resultMap>