Interface mapping of MyBatis source code analysis

MyBatis provides the function of interface mapping, which enables us to call the SQL statement configured by XML or interface annotation in an object-oriented manner. Mapping interfaces such as AccountMapper do not implement classes:

public interface AccountMapper{
    int login(Map<String,Object> map);
}

So what is the principle of interface mapping?

First of all, the mapping interface does not implement a class, it is a proxy object generated by JDK dynamic proxy. MyBatis can configure the package where the mapping interface is located in the mybatis-config.xml file:

<mappers>
    <package name="qy.ssh.mapper"/>
</mappers>

When parsing, all mapping interfaces under the package can be parsed into proxy factory objects (MapperProxyFactory):

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
knownMappers.put(type, new MapperProxyFactory<T>(type));

Second, look at the mapping interface generation process:

// DefaultSqlSession:默认会话实现类
@Override
public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
}

// Configuration:MyBatis核心类
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
}

// MapperRegistry:接口注册器
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
    // 通过代理工厂生成代理对象
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}
// MapperProxyFactory:代理工厂
public T newInstance(SqlSession sqlSession) { 
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
    // 调用JDK动态代理类Proxy生成代理对象
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

Focusing on the last method, this method dynamically generates the mapped interface object through the Proxy proxy class:

Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy)

The focus is on the incoming third parameter, MapperProxy. This parameter needs to implement the InvocationHandler interface, rewrite the invoke method, and the generated proxy object calls the method of the object. We can see from the generated class file that the invoke method is essentially called. . The proxy mode can be implemented without changing the proxy object, and adding new functions is the code added in the invoke method (such as Spring AOP functions).

Finally, the core, how does the proxy object of the mapped interface execute the method? From the above analysis, we can see that the invoke method of MapperProxy is actually called:

// MapperProxy 实现了InvocationHandler接口
// 重写了invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

Looking at the last line of the invoke method, we can see that the execute method of MapperMethod is actually called:

// MapperMethod
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
    // 调用sqlSession的insert方法
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        // 调用sqlSession的update方法
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        // 调用sqlSession的delete方法
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
    // 调用sqlSession的select*方法
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          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());
    }
    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;
  }

The above code tells us that the execute method of MapperMethod finally executes the CRUD method of DefaultSqlSession, and the CRUD method needs to pass in the statement parameter (the ID of the fully qualified SQL statement), and the statement is stored in the mappedStatements of Configuration:

protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");

The mappedStatements are generated by parsing the mapper file when the MyBatis core class Configuration is initialized. The specific initialization process will be available in the following blog posts.

It can be seen from the above analysis that the mapping interface generates a proxy object through the proxy mode, and the method of accessing the proxy object essentially accesses the configured SQL statement generated by parsing the mapper file.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325683332&siteId=291194637