Parameter Analysis of mybatis Query Statement

  • 2021-07-16 02:34:36
  • OfStack

1. Preface

As we know from the previous, when querying through getMapper, it will finally pass through mapperMehod class, and the parameters sent from the docking port will be parsed in this class, then transmitted to the corresponding position, matched with the parameters in sql, and finally obtained the results. There are several ways to pass parameters for mybatis (Rowbounds and ResultHandler are ignored here).

1. javabean type parameter

2. Non-javabean type parameters

Note that this article is based on mybatis version 3.5. 0.

1. Storage of parameters

2. Assignment of parameters in sql statement

The following will focus on these two aspects

2. Storage of parameters

Look at the following 1 piece of code first


@Test
 public void testSelectOrdinaryParam() throws Exception{
 SqlSession sqlSession = MybatisUtil.getSessionFactory().openSession();
 UserMapper mapper = sqlSession.getMapper(UserMapper.class);
 List<User> userList = mapper.selectByOrdinaryParam(" Zhang 31 No. ");
 System.out.println(userList);
 sqlSession.close();
 }
 List<User> selectByOrdinaryParam(String username); // mapper Interface 
 <select id="selectByOrdinaryParam" resultMap="BaseResultMap">
 select
 <include refid="Base_Column_List"/>
 from user
 where username = #{username,jdbcType=VARCHAR}
 </select>

Some people may wonder that this mapper interface does not have @ Param annotation. How can you directly bring parameter names in the mapper configuration file? Isn't it an error?

In mybatis, for a single parameter, it is no problem to use the parameter name directly. If it is multiple parameters, it can't be like this. Let's understand the parsing process of mybatis. Please see the following code, which is located in the internal class MethodSignature constructor of MapperMehod class


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.returnsOptional = Optional.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);
 //  Parameter resolution class 
 this.paramNameResolver = new ParamNameResolver(configuration, method);
 }

Parameter storage and parsing are all operated by ParamNameResolver class. First, look at the constructor of this class


/**
 * config  Global Profile Center 
 * method  The actual implementation method, that is, mapper Abstract methods in interfaces 
 * 
 */
public ParamNameResolver(Configuration config, Method method) {
 //  Get method All parameter types in 
 final Class<?>[] paramTypes = method.getParameterTypes();
 //  Gets the annotations contained in the parameters, mainly for @Param Prepare for annotations 
 final Annotation[][] paramAnnotations = method.getParameterAnnotations();
 final SortedMap<Integer, String> map = new TreeMap<>();
 //  In fact, the value obtained here is the number of parameters. That is 2 Row length of dimension array 
 int paramCount = paramAnnotations.length;
 // get names from @Param annotations
 for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
 //  Exclude RowBounds And ResultHandler Two types of parameters 
 if (isSpecialParameter(paramTypes[paramIndex])) {
 // skip special parameters
 continue;
 }
 String name = null;
 //  If the parameter contains @Param Annotation, use only @Param The value of the annotation is used as the parameter name 
 for (Annotation annotation : paramAnnotations[paramIndex]) {
 if (annotation instanceof Param) {
  hasParamAnnotation = true;
  name = ((Param) annotation).value();
  break;
 }
 }
 //  That is, the parameter does not have @Param Annotation 
 if (name == null) {
 //  The actual name of the parameter, in fact, this value defaults to true You can view the details Configuration Class, of course, it can also be configured to close in the configuration file 
 //  If jdk Be in 1.8 Version, and compile time with -parameters  Parameter, the actual parameter name is obtained, such as methodA(String username)
 //  What you get is username, Otherwise, what you get is args0  The following number is the position of the parameter 
 if (config.isUseActualParamName()) {
  name = getActualParamName(method, paramIndex);
 }
 //  If none of the above conditions are met, the parameter name is configured to  0,1,2../
 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);
 }

The function of this constructor is to encapsulate the parameter name and get a "parameter position- > Parameter name ", the purpose of this is to replace the parameter value. We also know that the actual passed parameter is an Object array structure, which can also be understood as an map structure. That is, index- > Parameter value, which corresponds to the previous map structure, and finally you can get a parameter name-- > 1 correspondence of parameter values.


public Object execute(SqlSession sqlSession, Object[] args) {
 Object result;
 switch (command.getType()) {
 //  Ignore other situations 
 case SELECT:
 //  The parameters here contain resultHandler , no discussion for the time being 
 if (method.returnsVoid() && method.hasResultHandler()) {
  executeWithResultHandler(sqlSession, args);
  result = null;
 } else if (method.returnsMany()) {// 1 ,   The returned result is of collection type or array type, which is suitable for most cases 
  result = executeForMany(sqlSession, args);
 } else if (method.returnsMap()) {//  The result returned is Map Type 
  result = executeForMap(sqlSession, args);
 } else if (method.returnsCursor()) {
  result = executeForCursor(sqlSession, args);
 } else {// 2 Returns the result javabean Type, or ordinary base type and its wrapper class, etc.  
  Object param = method.convertArgsToSqlCommandParam(args);
  result = sqlSession.selectOne(command.getName(), param);
  //  Right java8 In optional Support was given 
  if (method.returnsOptional() &&
  (result == null || !method.getReturnType().equals(result.getClass()))) {
  result = Optional.ofNullable(result);
  }
 }
 break;
 default:
 throw new BindingException("Unknown execution method for: " + command.getName());
 }
 if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
 throw new BindingException("Mapper method '" + command.getName()
  + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
 }
 return result;
 }

Here, we mainly analyze the 1 situation. For the 2 case, that is, the parameter assignment case to be mentioned next, but first introduce the 1 result brought by method. convertArgsToSqlCommandParam


public Object convertArgsToSqlCommandParam(Object[] args) {
 return paramNameResolver.getNamedParams(args);
 }

public Object getNamedParams(Object[] args) {
 final int paramCount = names.size();
 if (args == null || paramCount == 0) {
 return null;
 } else if (!hasParamAnnotation && paramCount == 1) {// 1
 return args[names.firstKey()];
 } else {
 final Map<String, Object> param = new ParamMap<>();
 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;
 }
 }

You can clearly know that you finally called the getNamedPaams method of the ParamNameResolver class. The main function of this method is to change the original parameter position- > Parameter name mapping to parameter name-- > Parameter value, and add a new parameter name and a corresponding relationship between parameter value. I.e.

param1 - > Parameter value 1

param2 -- > Parameter value 2

Of course, if there is only one parameter, such as part 1 of the code, if the parameter has no @ Param annotation and only one parameter, the above-mentioned one object relationship will not be added, which is the reason why the parameter name can be written directly in sql for a single parameter. Go back to the front below


private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
 List<E> result;
 //  Gets the corresponding 1 A mapping relationship, param The type may be map Or null Or the actual type of the parameter 
 Object param = method.convertArgsToSqlCommandParam(args);
 if (method.hasRowBounds()) {
 RowBounds rowBounds = method.extractRowBounds(args);
 result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
 } else {
 result = sqlSession.<E>selectList(command.getName(), param);
 }
 //  If the return result type and the method The return result type of does not 1 To, the data structure is converted 
 //  In fact, it is result The returned result is not List Type, but other collection types or array types 
 if (!method.getReturnType().isAssignableFrom(result.getClass())) {
 if (method.getReturnType().isArray()) {//  Is an array result 
 return convertToArray(result);
 } else {//  Other collection types 
 return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
 }
 }
 return result;
 }

The code is not complicated, that is, the corresponding relationship of parameters will be passed in, and the final results will be obtained, and the results will be converted according to the actual needs.

3. Assignment of parameters in sql statement

In fact, it is also involved in the previous blog. Parameter assignment position in the DefaultParameterHandler class, you can see the previous 1 blog, here do not do too much introduction, portal mybatis query behind the encapsulated data

Summarize


Related articles: