preface
Mybatis (Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis How this all relates to xxmapper.xml.
Method name mapping
The reason for caching MapperMethod is that you need to instantiate two classes, SqlCommand and MethodSignature, which take some time to instantiate; The mapping of method names is performed when SqlCommand is instantiated.
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); }}}Copy the code
First, get the name of the method and the class that defines this method, generally such class is xxxMapper class; There is a difference between the declaringClass class and mapperInterface, mainly because this method can be a parent class while mapperInterface is a subclass. So if the parent xxxMapper is defined, it can also be mapped, so you can see the related 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) {returnms; }}}returnnull; }}Copy the code
Com.xx.mapper. XXMapper+ method name, which corresponds to namespace+statementID in xxmapper. XML. If it can be found, it returns an MappedStatement in the Configuration file, which can temporarily be a label block in xxmapper.xml. If it can’t find it, it might be in the parent class, so you can see that recursion is used until you look for all the parent classes, and if it still can’t find it, null is returned; If the MappedStatement cannot be found, the MappedStatement will check to see if the method has the @flush annotation. If the MappedStatement is specified as Flush, the MappedStatement will raise an exception. Find MappedStatement to get command types from it. All types include:
public enum SqlCommandType {
UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
}
Copy the code
INSERT, UPDATE, DELETE, SELECT are the same tags we defined in xxmapper.xml;
The method signature
The other object in the cache is MethodSignature, which literally means MethodSignature. It contains method return values, parameters, and so on.
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); }Copy the code
The first is the Type of the return value. We need more specific types when querying, such as object Type, basic data Type, array Type, list Type, Map Type, cursor Type, void Type. ParameterizedType; TypeVariable; GenericArrayType; WildcardType; ParameterizedType: represents a ParameterizedType, such as Collection. TypeVariable: represents a generic type such as T; GenericArrayType: Array type of a type variable; WildcardType: a WildcardType expression, such as? ,? Extends the Number; After obtaining the specific type, four identifiers are created according to the type: returnsMany, returnsMap, returnsVoid, and returnsCursor, respectively: returnsMany: returns a list or array; ReturnsMap: Returns a Map; ReturnsVoid: no return value; ReturnsCursor: Returns a cursor; In addition to the above parameters, three parameters are defined: RowBounds position in all parameters, ResultHandler position in all parameters, and a parameter parsing class ParamNameResolver is instantiated to convert parameters to SQL command parameters. Note: RowBounds and ResultHandler are special parameters that do not map to xxmapper.xml and are used to handle paging and reprocessing results, respectively;
Parameter mapping processing
Parameter mapping is handled primarily in ParamNameResolver. While instantiating MethodSignature, a ParamNameResolver is initialized with the following constructor:
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 annotationsfor (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);
}
Copy the code
First get the type of the parameter, then get the annotation of the parameter; And then we iterate through the annotation, and as we iterate through it we check if the parameter types are RowBounds and ResultHandler, and as we said earlier, these are two special types that are not used for parameter mapping, so we filter them out, and then we get the values in the annotation like @param (“id”) value=”id”, If there is no annotation value, the mybatis-config. XML file is checked to see if useActualParamName is configured. This is a switch that indicates whether to use the actual parameter name. To represent a name; The following has been an example to illustrate, such as the following method:
public Blog selectBlog3(@Param("id") long id, @Param("author") String author);
Copy the code
{0=id, 1=author}, if @param is removed, it looks like this:
public Blog selectBlog3(long id, String author);
Copy the code
Names are: {0=arg0, 1=arg1} if useActualParamName is turned off:
<setting name="useActualParamName" value="false"/>
Copy the code
Names: {0=0, 1=1}; Names here is just the initialization information, the actual parameters mapped to xxmapper.xml are processed 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++; }returnparam; }}Copy the code
This method iterates over names, checking for null and one parameter before traversing, and does not comment on the parameter. This method returns the value of the parameter without a key. In this case, xxmapper. XML does not care about the parameter name. If two of the above scenarios are not used to iterate over names and write two keys to a Map, the values will change as follows:
{author=zhaohui, id=158, param1=158, param2=zhaohui}
Copy the code
This is when the annotation name is set;
{arg1=zhaohui, arg0=158, param1=158, param2=zhaohui}
Copy the code
This is when the annotation name is not set, but the useActualParamName switch is turned on;
{0=158, 1=zhaohui, param1=158, param2=zhaohui}
Copy the code
This is when the annotation name is not set and the useActualParamName switch is turned off;
Xxmapper.xml can be configured as xxmapper. XML.
<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>
Copy the code
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>
Copy the code
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>
Copy the code
It is because Mybatis provides a variety of key values when initializing parameter mapping that it is more convenient for developers to set values flexibly. Although there are multiple key values to choose from, I think it is more standard to set explicit annotations;
conclusion
This article focuses on the instantiation process of SqlCommand and MethodSignature. SqlCommand focuses on the mapping of method name through the interface path + method name and namespace+statementID in xxmapper. XML. And we’re going to get to the problem of recursive superclasses; Then there is the method signature, which creates four identifiers from the method’s return value type; Finally, the mapping of parameters is discussed. Mybatis provides a variety of mapping keys for developers.
Sample code address
Github