Mybatis Mapper interface source code analysis

Mybatis Mapper interface source code analysis

TSMYK Java technology programming

The first address of this article is a personal blog
https://my.oschina.net/mengyuankan/blog/2873220

related articles

Mybatis analysis configuration file source code analysis
Mybatis type conversion source code analysis
Mybatis database connection pool source code analysis

Preface

When using Mybatis, we only need to write the corresponding interface, that is, the Mapper interface of the dao layer. Without writing the implementation class, Mybatis can find the corresponding SQL configured in the xml file according to the corresponding method name in the interface, method parameters and SQL In the SQL in xml, we can bind parameters through #{0}, #{1}, and we can also bind parameters through #{arg0}, #{arg1}. It can be bound by the real parameter names in the method, such as name, age, etc., and can also be bound by #{param1}, #{param2}, etc. Next, let's see how the source code of Mybatis is implemented.

Source code analysis

In Mybatis, the source code for parsing the Mapper interface is mainly under the binding package. There are 4 classes in this package, plus a tool class ParamNameResolver for method parameter name resolution. There are 5 classes in total. The amount of code is not much. Analyze these categories at once.
Mybatis Mapper interface source code analysis
Let's briefly look at these categories first:

  1. BindingException: custom exception, ignored
  2. MapperMethod: In this class, the information of the corresponding method of the Mapper interface and the information of the corresponding SQL statement are encapsulated. The MapperMethod class can be regarded as the connection bridge between the Mapper interface and the SQL statement in the configuration file. It is in these classes The most important class has more codes, while the other classes have very few codes.
  3. MapperProxy: It is the proxy object of the Mapper interface. When using Mybatis, we do not need to implement the Mapper interface. It is implemented using the dynamic proxy of the JDK.
  4. MapperProxyFactory: Factory class of MapperProxy class, used to create MapperProxy
  5. MapperRegistry: It is the registration center of the Mybatis interface and its proxy object factory. When Mybatis is initialized, it will load the configuration file and Mapper interface information to register to this class.
  6. ParamNameResolver This class is not a class under the binding package. It is a tool class under the reflection package. It is mainly used to resolve interface method parameters.
    Next, look at the implementation process of each class:

MapperProxy

First look at the MapperProxy class, which is the proxy object of the Mapper interface and implements the InvocationHandler interface, that is, the dynamic proxy of the JDK is used to generate proxy objects for the Mapper interface.


public class MapperProxy<T> implements InvocationHandler, Serializable {

  // 关联的 sqlSession 对象
  private final SqlSession sqlSession;
  // 目标接口,即 Mapper 接口对应的 class 对象
  private final Class<T> mapperInterface;
  // 方法缓存,用于缓存 MapperMethod对象,key 为 Mapper 接口中对应方法的 Method 对象,value 则是对应的 MapperMethod,MapperMethod 会完成参数的转换和 SQL 的执行功能
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  // 代理对象执行的方法,代理以后,所有 Mapper 的方法调用时,都会调用这个invoke方法
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     // 并不是每个方法都需要调用代理对象进行执行,如果这个方法是Object中通用的方法,则无需执行
     if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      // 如果是默认方法,则执行默认方法,Java 8 提供了默认方法
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
     // 从缓存中获取 MapperMethod 对象,如果缓存中没有,则创建一个,并添加到缓存中
     final MapperMethod mapperMethod = cachedMapperMethod(method);
     // 执行方法对应的 SQL 语句
     return mapperMethod.execute(sqlSession, args);
  }
  // 缓存 MapperMethod 
  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
}

The above is the main code of the MapperProxy proxy class. It should be noted that the relevant methods in MapperMethod are called. MapperMethod will be analyzed later. First, know that it is the SQL corresponding to the conversion of method parameters and the execution method. One more point, When executing the target method, if it is a method in Object, the target method will be executed directly, if it is the default method, the relevant logic of the default method will be executed, otherwise the target method will be executed using a proxy object

MapperProxyFactory

After reading the above-mentioned MapperProxy proxy class, MapperProxyFactory is used to create the proxy class, which is a factory class.


public class MapperProxyFactory<T> {
  // 当前的 MapperProxyFactory 对象可以创建的 mapperInterface 接口的代理对象
  private final Class<T> mapperInterface;

  // MapperMetho缓存
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

  // 创建 mapperInterface 的代理对象
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
}

A factory class, mainly used to create proxy objects of the Mapper interface, the code is simple, there is nothing to say.

MapperRegistry

MapperRegistry It is the registration center of the Mapper interface and its corresponding proxy factory object. When Mybatis is initialized, it will load the configuration file and Mapper interface information and register it in the MapperRegistry class. When a certain SQL needs to be executed, it will be obtained from the registry first The proxy object of the Mapper interface.


public class MapperRegistry {
  // 配置对象,包含所有的配置信息
  private final Configuration config;

  // 接口和代理对象工厂的对应关系,会用工厂去创建接口的代理对象
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

  // 注册 Mapper 接口
  public <T> void addMapper(Class<T> type) {
    // 是接口,才进行注册
    if (type.isInterface()) {
      // 如果已经注册过了,则抛出异常,不能重复注册
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      // 是否加载完成的标记
      boolean loadCompleted = false;
      try {
        // 会为每个 Mapper 接口创建一个代理对象工厂
        knownMappers.put(type, new MapperProxyFactory<T>(type));

        // 下面这个先不看,主要是xml的解析和注解的处理
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        // 如果注册失败,则移除掉该Mapper接口
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

  // 获取 Mapper 接口的代理对象,
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 从缓存中获取该 Mapper 接口的代理工厂对象
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
     // 如果该 Mapper 接口没有注册过,则抛异常
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    //使用代理工厂创建 Mapper 接口的代理对象
    return mapperProxyFactory.newInstance(sqlSession);
  }  
}

The above is the code registered by MapperRegistry, which mainly registers the Mapper interface and obtains the proxy object of the Mapper interface, which is easy to understand.

ParamNameResolver

ParamNameResolver is not a class under the binding package. It is a tool class under the reflection package. It is mainly used to resolve interface method parameters.
That is, in the method parameters, the parameter is the first one, that is, the corresponding relationship between the parameter name and the index is parsed; the code in this class is more complicated than the above several classes, and it is a bit convoluted. I wrote the main method To help understand, but the amount of code is not much.

The main methods in this class are mainly the construction method and the getNamedParams method. Let’s look at the other methods first:


public class ParamNameResolver {
  // 参数前缀,在 SQL 中可以通过 #{param1}之类的来获取
  private static final String GENERIC_NAME_PREFIX = "param";

  // 参数的索引和参数名称的对应关系,有序的,最重要的一个属性
  private final SortedMap<Integer, String> names;
  // 参数中是否有 @Param 注解
  private boolean hasParamAnnotation;

  // 获取对应参数索引实际的名称,如 arg0, arg1,arg2......
  private String getActualParamName(Method method, int paramIndex) {
      Object[] params = (Object[]) GET_PARAMS.invoke(method);
      return (String) GET_NAME.invoke(params[paramIndex]);
  }
  // 是否是特殊参数,如果方法参数中有 RowBounds 和 ResultHandler 则会特殊处理,不会存入到 names 集合中
  private static boolean isSpecialParameter(Class<?> clazz) {
    return RowBounds.class.isAssignableFrom(clazz) || ResultHandler.class.isAssignableFrom(clazz);
  }

  // 返回所有的参数名称 (toArray(new String[0])又学习了一种新技能)
  public String[] getNames() {
    return names.values().toArray(new String[0]);
  }

The above code is some auxiliary methods of ParamNameResolver. The most important is the names attribute, which is used to store the correspondence between the parameter index and the parameter name. It is a map, but there are exceptions. If the parameter contains two types of RowBounds and ResultHandler, then They will not put their indexes and corresponding relationships into the names collection, as follows:


aMethod(@Param("M") int a, @Param("N") int b) -- names = {{0, "M"}, {1, "N"}}
aMethod(int a, int b) -- names = {{0, "0"}, {1, "1"}}
aMethod(int a, RowBounds rb, int b) -- names = {{0, "0"}, {2, "1"}}

Construction method, now let’s look at the construction method of ParamNameResolver. When the construction method is called to create this type of object, the parameters of the method will be parsed, and the parsing result will be put into the names data. The code is as follows:

Focus


  public ParamNameResolver(Configuration config, Method method) {
    // 方法所有参数的类型
    final Class<?>[] paramTypes = method.getParameterTypes();
    // 所有的方法参数,包括 @Param 注解的参数
    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++) {
      // 不处理 RowBounds 和 ResultHandler 这两种特殊的参数
      if (isSpecialParameter(paramTypes[paramIndex])) {
        continue;
      }
      String name = null;
      for (Annotation annotation : paramAnnotations[paramIndex]) {
        // 如果参数被 @Param 修饰
        if (annotation instanceof Param) {
          hasParamAnnotation = true;
          // 则参数名称取其值
          name = ((Param) annotation).value();
          break;
        }
      }
      // 如果是一般的参数
      if (name == null) {
        // 是否使用真实的参数名称,true 使用,false 跳过
        if (config.isUseActualParamName()) {
          // 如果为 true ,则name = arg0, arg1 之类的
          name = getActualParamName(method, paramIndex);
        }
        // 如果上述为false,
        if (name == null) {
          // name为参数索引,0,1,2 之类的
          name = String.valueOf(map.size());
        }
      }
      // 存入参数索引和参数名称的对应关系
      map.put(paramIndex, name);
    }
    // 赋值给 names 属性
    names = Collections.unmodifiableSortedMap(map);
  }

After reading the above structure, test the main method, there are the following methods:


Person queryPerson(@Param("age") int age, String name, String address, @Param("money") double money);

In the above method, if the real name is used, that is, when config.isUseActualParamName() is true, after parsing, the names attribute is printed as follows:


{0=age, 1=arg1, 2=money, 3=arg3}

If config.isUseActualParamName() is false, after parsing, the names attribute will be printed as follows:


{0=age, 1=1, 2=money, 3=3}

Now that the parameters of the method are parsed, if the parameter value of the incoming method comes in, how to get it? It is the getNamedParams method in this class:


  public Object getNamedParams(Object[] args) {
    // 参数的个数
    final int paramCount = names.size();
    if (args == null || paramCount == 0) {
      return null;
     // 如果参数没有被 @Param 修饰,且只有一个,则直接返回
    } 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()) {
        // key = 参数名称,value = 参数值
        param.put(entry.getValue(), args[entry.getKey()]);

        final String genericParamName = "param"+ String.valueOf(i + 1);
        // 默认情况下它们将会以它们在参数列表中的位置来命名,比如:#{param1},#{param2}等
        if (!names.containsValue(genericParamName)) {
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
     // 返回参数名称和参数值的对应关系,是一个 map 
      return param;
    }
  }

Now test it in the main method:

If config.isUseActualParamName() is true and the method is parsed by the constructor, the corresponding relationship between the parameter index and the name is:


{0=age, 1=arg1, 2=money, 3=arg3}

Now the parameters are:


Object[] argsArr = {24, "zhangsan", 1000.0, "chengdou"};

Now call the getNamedParams method to bind the method name and method value. The results obtained are as follows. Note that the method returns an Object, which is actually a map:


{age=24, param1=24, arg1=zhangsan, param2=zhangsan, money=1000.0,  param3=1000.0, arg3=chengdou,  param4=chengdou}

So in the SQL in xml, you can get the value through the corresponding #{name}, or you can get the value through #{param1}, etc.

In another case, config.isUseActualParamName() is false. After parsing, the correspondence between parameter index and name is:


{0=age, 1=1, 2=money, 3=3}

After that, what does the binding of parameters and parameter values ​​look like? The main method is still tested as follows, and the parameter is the above argsArr parameter:


{age=24, param1=24, 1=zhangsan, param2=zhangsan, money=1000.0, param3=1000.0, 3=chengdou param4=chengdou}

In SQL, parameter values ​​can also be obtained through #{0}, #{1} and the like.

Therefore, after the ParamNameResolver class parses the Mapper interface, we can use #{name}, #{param1}, #{0} and other methods to obtain the corresponding parameter value in SQL.

MapperMethod

After understanding the above ParamNameResolver tool class, it is easy to understand MapperMethod.

In this class, the information of the corresponding method of the Mapper interface and the information of the corresponding SQL statement are encapsulated. The MapperMethod class can be regarded as the connection bridge between the Mapper interface and the SQL statement in the configuration file.

There are only two properties in this class, which correspond to two internal classes, SqlCommand and MethodSignature respectively. SqlCommand records the name and type of the SQL statement, and MethodSignature is the corresponding method information in the Mapper interface:


public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }
}

Let's take a look at these two internal classes first.

SqlCommand


public static class SqlCommand {
// SQL 的名称,是接口的全限定名+方法名组成
private final String name;
// SQL 的类型,取值:UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
private final SqlCommandType type;

public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
  // SQL 名称
  String statementName = mapperInterface.getName() + "." + method.getName();
  // MappedStatement  封装了 SQL 语句的相关信息
  MappedStatement ms = null;
  // 在配置文件中检测是否有该 SQL 语句
  if (configuration.hasStatement(statementName)) {
    ms = configuration.getMappedStatement(statementName);
  } else if (!mapperInterface.equals(method.getDeclaringClass())) { 
    // 是否父类中有该 SQL 的语句
    String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
    if (configuration.hasStatement(parentStatementName)) {
      ms = configuration.getMappedStatement(parentStatementName);
    }
  }
  // 处理 @Flush 注解
  if (ms == null) {
    if(method.getAnnotation(Flush.class) != null){
      name = null;
      type = SqlCommandType.FLUSH;
    }
  } else {
    // 获取 SQL 名称和类型
    name = ms.getId();
    type = ms.getSqlCommandType();
  }
}

MethodSignature


  public static class MethodSignature {

    private final boolean returnsMany; // 方法的返回值为 集合或数组
    private final boolean returnsMap;  // 返回值为 map
    private final boolean returnsVoid;  // void
    private final boolean returnsCursor; // Cursor
    private final Class<?> returnType;  // 方法的返回类型
    private final String mapKey; // 如果返回值为 map,则该字段记录了作为 key 的列名
    private final Integer resultHandlerIndex;  // ResultHandler 参数在参数列表中的位置
    private final Integer rowBoundsIndex;  // RowBounds参数在参数列表中的位置

    // 用来解析接口参数,上面已介绍过
    private final ParamNameResolver paramNameResolver;

    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);
    }

    // 根据参数值来获取参数名称和参数值的对应关系 是一个 map,看上面的main方法测试
    public Object convertArgsToSqlCommandParam(Object[] args) {
      return paramNameResolver.getNamedParams(args);
    }
}

MapperMethod

Next, let’s look at MapperMethod. The core method is the execute method, which is used to execute the SQL corresponding to the method:


  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        // insert 语句,param 为 参数名和参数值的对应关系
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          // void 类型且方法有 ResultHandler 参数,调用 sqlSession.select 执行
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          // 返回集合或数组,调用 sqlSession.<E>selectList 执行
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          // 返回 map ,调用 sqlSession.<K, V>selectMap
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    return result;
  }

The above is the main implementation of the MapperMethod class, which is to get the name and parameters of the corresponding interface, call the corresponding method value of sqlSession and execute the corresponding SQL to get the result. There are some auxiliary methods in this class, which can be ignored.

to sum up

The above is the underlying analysis of the Mapper interface, that is, the binding module. Mybatis will use the dynamic proxy of the JDK to create a proxy object for each Mapper interface, and use the ParamNameResolver tool class to parse the parameters of the Mapper interface, so that the SQL in XML can use three There are two ways to get the value of the parameter, #{name}, #{0} and #{param1}. When the interface parameter parsing is completed, there will be an execute method of MapperMethod to call the name of the interface, that is, the corresponding name and parameters of the SQL Related methods of sqlSession to execute SQL to obtain results.

Guess you like

Origin blog.51cto.com/15077536/2608582