Preface
Above How the Mapper interface of Mybatis executes SQL You learned that Mapper executes SQL through a dynamic proxy, but there is no detailed description of how the method maps, including method names, return values, parameters, and so on; how these relate to xxMapper.xml.
Method Name Mapping
The purpose of caching MapperMethod s mentioned above is to instantiate two classes, SqlCommand and MethodSignature, which take some time to instantiate; while the mapping of method names is when instantiating SqlCommand, you can see the construction method:
private final String name; private final SqlCommandType type; public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) { final String methodName = method.getName(); final Class<?> declaringClass = method.getDeclaringClass(); MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration); if (ms == null) { if (method.getAnnotation(Flush.class) != null) { name = null; type = SqlCommandType.FLUSH; } else { throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName); } } else { name = ms.getId(); type = ms.getSqlCommandType(); if (type == SqlCommandType.UNKNOWN) { throw new BindingException("Unknown execution method for: " + name); } } }
The name of the method and the class that defines it are obtained first, usually the xxxMapper class. Note that the declaringClass class and the mapperInterface are different here, mainly because the method can be a method within the parent class and the mapperInterface is a subclass. So if the parent xxxMapper is defined, it can also be mapped, so you can see the code:
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName, Class<?> declaringClass, Configuration configuration) { String statementId = mapperInterface.getName() + "." + methodName; if (configuration.hasStatement(statementId)) { return configuration.getMappedStatement(statementId); } else if (mapperInterface.equals(declaringClass)) { return null; } for (Class<?> superInterface : mapperInterface.getInterfaces()) { if (declaringClass.isAssignableFrom(superInterface)) { MappedStatement ms = resolveMappedStatement(superInterface, methodName, declaringClass, configuration); if (ms != null) { return ms; } } } return null; } }
You can see that the method name mapping is not just a name, but also an interface name preceded by something like:com
The.xx.mapper.XXMapper+method name, which corresponds to the namespace+statementID in xxMapper.xml; returns a MappedStatement directly in configuration if it can be found, temporarily simply as a tag block in xxMapper.xml; if no description is found, recursion can be found here until it is found in all parent classes, and if it is returnedNo return null was found;
Next, if the corresponding MappedStatement is not found, the code in the previous section looks to see if the method has a @Flush annotation, or throws an exception if the specified command type is FLUSH; find MappedStatement to get the command type from it, all of which include:
public enum SqlCommandType { UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH; }
INSERT, UPDATE, DELETE, SELECT are actually tags that we define in xxMapper.xml;
Method Signature
Another object cached is MethodSignature, which is a method signature, and contains the return values, parameters, and so on. You can see its constructor:
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) { Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface); if (resolvedReturnType instanceof Class<?>) { this.returnType = (Class<?>) resolvedReturnType; } else if (resolvedReturnType instanceof ParameterizedType) { this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType(); } else { this.returnType = method.getReturnType(); } this.returnsVoid = void.class.equals(this.returnType); this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray(); this.returnsCursor = Cursor.class.equals(this.returnType); this.mapKey = getMapKey(method); this.returnsMap = this.mapKey != null; this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class); this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class); this.paramNameResolver = new ParamNameResolver(configuration, method); }
The first is to get the return value of the type Type, we need more specific types when querying, such as object type, basic data type, array type, list type, Map type, cursor type, void type; so here we need the type of sub-interface classes including: ParameterizedType, TypeVariable<D>, GenericArrayType, WThe four ildcardTypes represent:
ParameterizedType: Represents a parameterized type, such as Collection;
TypeVariable: Represents a generic type such as T;
GenericArrayType: Array type of type variable;
WildcardType: A wildcard type expression, such as?,? extends Number;
After getting the specific type, four identities are created based on the type: returnsMany, returnsMap, returnsVoid, returnsCursor, representing:
returnsMany: returns a list or array;
returnsMap: Return a Map;
returnsVoid: No return value;
returnsCursor: Returns a cursor;
In addition to the parameters described above, three parameters are defined: the position of RowBounds in all parameters, the position of ResultHandler parameters in all parameters, and the instantiation of a parametric parsing class ParamNameResolver to be used as a parameter to be a sql command parameter; Note: RowBounds and ResultHandler are two special parameters and are not mapped to xMapper.xmlParameters, which are used to process paging and reprocessing results, respectively;
Parameter Mapping Processing
Parameter mapping is mainly handled in ParamNameResolver, which initializes a ParamNameResolver while instantiating the MethodSignature with the following constructors:
private final SortedMap<Integer, String> names; private boolean hasParamAnnotation; public ParamNameResolver(Configuration config, Method method) { final Class<?>[] paramTypes = method.getParameterTypes(); final Annotation[][] paramAnnotations = method.getParameterAnnotations(); final SortedMap<Integer, String> map = new TreeMap<Integer, String>(); int paramCount = paramAnnotations.length; // get names from @Param annotations for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) { if (isSpecialParameter(paramTypes[paramIndex])) { // skip special parameters continue; } String name = null; for (Annotation annotation : paramAnnotations[paramIndex]) { if (annotation instanceof Param) { hasParamAnnotation = true; name = ((Param) annotation).value(); break; } } if (name == null) { // @Param was not specified. if (config.isUseActualParamName()) { name = getActualParamName(method, paramIndex); } if (name == null) { // use the parameter index as the name ("0", "1", ...) // gcode issue #71 name = String.valueOf(map.size()); } } map.put(paramIndex, name); } names = Collections.unmodifiableSortedMap(map); }
First you get the type of the parameter, then get the comment for the parameter; then you iterate through the comment, checking to see if the parameter types are RowBounds and ResultHandler, which, as mentioned earlier, are two special types that are not used for parameter mapping, so you filter them out here and get the value in the comment such as @Param("id") ="id". If notAnnotation value checks whether useActualParamName is configured in mybatis-config.xml. This is a switch to indicate whether the real parameter name is used, which is on by default, and the subscripts 0, 1, 2 to indicate the name if the switch is off. An example is given below, such as the following:
public Blog selectBlog3(@Param("id") long id, @Param("author") String author);
The corresponding names are: {0=id, 1=author}, if removed @Param As follows:
public Blog selectBlog3(long id, String author);
The corresponding names are: {0=arg0, 1=arg1}, if the useActualParamName switch is turned off:
<setting name="useActualParamName" value="false"/>
The corresponding names are: {0=0, 1=1}; names here are just initialization information, and the parameters that really map to xxMapper.xml are also handled in another method in ParamNameResolver:
public Object getNamedParams(Object[] args) { final int paramCount = names.size(); if (args == null || paramCount == 0) { return null; } else if (!hasParamAnnotation && paramCount == 1) { return args[names.firstKey()]; } else { final Map<String, Object> param = new ParamMap<Object>(); int i = 0; for (Map.Entry<Integer, String> entry : names.entrySet()) { param.put(entry.getValue(), args[entry.getKey()]); // add generic param names (param1, param2, ...) final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1); // ensure not to overwrite parameter named with @Param if (!names.containsValue(genericParamName)) { param.put(genericParamName, args[entry.getKey()]); } i++; } return param; } }
This method traverses names, checks to see if there is only one parameter before traversing, and does not annotate the parameter, which returns the parameter value directly without a key, in which case the parameter name corresponding to xxMapper.xml is not really concerned, and any name can be mapped directly; otherwise, names will be traversedWriting two key values to a Map, or the three cases above, will change the values as follows:
{author=zhaohui, id=158, param1=158, param2=zhaohui}
The above is the case when the name of the comment is set;
{arg1=zhaohui, arg0=158, param1=158, param2=zhaohui}
Above is the case where the annotation name is not set, but the useActualParamName switch is turned on;
{0=158, 1=zhaohui, param1=158, param2=zhaohui}
Above is the case where the annotation name is not set and the useActualParamName switch is turned off;
With these three scenarios, we can also configure xxMapper.xml differently. Here's how to configure xxMapper.xml according to the above three scenarios:
In the first case, xxMapper.xml can be configured as follows:
<select id="selectBlog3" parameterType="hashmap" resultType="blog"> select * from blog where id = #{id} and author=#{author} </select> <select id="selectBlog3" parameterType="hashmap" resultType="blog"> select * from blog where id = #{param1} and author=#{param2} </select>
In the second case, xxMapper.xml can be configured as follows:
<select id="selectBlog3" parameterType="hashmap" resultType="blog"> select * from blog where id = #{arg0} and author=#{arg1} </select> <select id="selectBlog3" parameterType="hashmap" resultType="blog"> select * from blog where id = #{param1} and author=#{param2} </select>
In the third case, xxMapper.xml can be configured as follows:
<select id="selectBlog3" parameterType="hashmap" resultType="blog"> select * from blog where id = #{0} and author=#{1} </select> <select id="selectBlog3" parameterType="hashmap" resultType="blog"> select * from blog where id = #{param1} and author=#{param2} </select>
It is because Mybatis provides a variety of key values when initializing parameter mapping, which makes it easier for developers to set flexible values; although multiple key value choices are provided, it is my opinion that setting explicit annotations is more specific.
summary
This paper focuses on the instantiation process of two classes: SqlCommand and and MethodSignature. SqlCommand focuses on the mapping of method names by interface path+method name and by namespace+statementID in xxMapper.xml, and addresses the problem of recursive parent class. Then it is the method signature, which creates four identities by the return value type of the method.Mybatis provides developers with a variety of mapping key s for parameters.