Analysis of MyBatis binding module

Posted by Dilbert137 on Tue, 22 Oct 2019 17:02:45 +0200

Package of binding function code

org.apache.ibatis.binding

binding module function

Encapsulating ibatis programming model In the ibatis programming model, SqlSession is the entry of sql execution. The practical method is sqlSession.selectOne(namespace+id, parameter list) (for example, sqlSession.selectOne("com.enjoylearning.mybatis.mapper.TUserMapper.selectByPrimaryKey", 2)), which is not easy to maintain and read.

Problems to be solved

  • Find the corresponding method in SqlSession (insert|update|select)
  • Find namespace and method name (2D coordinates)
  • Transfer parameters

Core class

  • Mapperregistry: the registry of the mapper interface and the corresponding proxy factory; is a member variable of Configuration
  • MapperProxyFactory: used to generate the strength object of mapper interface dynamic agent; ensure that the strength object of mapper is a local variable
  • MapperMethod: encapsulates the corresponding method information in mapper interface. MapperMethod object does not record any state, so it can be shared among multiple proxy objects. sqlCommand: encapsulates sql statement; mtethodsignature: encapsulates the input and return types of mapper interface.

Personal understanding

  • MapperRegistry's map < Class <? >, mapperproxyfactory <? > > knownmappers = new HashMap < > (); key: mapper's Class object value: the factory that produces the mapper's dynamic proxy example
  • Mapperproxyfactory: Map < method, mappermethod > methodcache key: Reflection class of method information of value method (sql statement, input parameter, return value)

Related code

public class MapperRegistry {

  private final Configuration config;//config object, mybatis globally unique
  //The relationship between mapper interface and MapperProxyFactory is recorded.
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }

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

/**
 * 
 * The instance object used to generate the dynamic agent of mapper interface;
 * @author Lasse Voss
 */
public class MapperProxyFactory<T> {

  //class object of mapper interface
  private final Class<T> mapperInterface;
//key is the method object of a method in the mapper interface, value is the corresponding MapperMethod, MapperMethod object does not record any state information, so it can be shared among multiple proxy objects.
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
public class MapperMethod {
  //Get the namespace, method name and SQL statement type of the method from configuration
  private final SqlCommand command;
  //Encapsulates information about the mapper interface method (input parameter, return type);
  private final MethodSignature method;

Procedure of getMapper

public class MapperProxyFactory<T> {

  //class object of mapper interface
  private final Class<T> mapperInterface;
//key is the method object of a method in the mapper interface, value is the corresponding MapperMethod, MapperMethod object does not record any state information, so it can be shared among multiple proxy objects.
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
	//Create a dynamic proxy object that implements the mapper interface
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
	 //Each call creates a new MapperProxy object
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

Two newInstance methods generate mapper's dynamic proxy object MapperProxy

public class MapperProxy<T> implements InvocationHandler, Serializable {
.......
@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {//If the method of Object itself is not enhanced
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    //Get mapperMethod object from cache, if not, create one and add it to cache
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //Call execute method to execute sql
    return mapperMethod.execute(sqlSession, args);
  }
  
  private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  }

As can be seen from the invoke method, mapperMethod calls the execute method.

public class MapperMethod {
 ......
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //According to the sql statement type and the parameters returned by the interface, different
    switch (command.getType()) {
      case INSERT: {
    	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()) {//Return value is void
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {//Return value is set or array
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {//The return value is map
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {//Return value is cursor
          result = executeForCursor(sqlSession, args);
        } else {//Handle the case of returning to a single object
          //Parsing parameters through parameter parser
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional() &&
              (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = OptionalUtil.ofNullable(result);
          }
        }
        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;
  }

Focus on result = sqlSession.selectOne(command.getName(), param); the usage of ibatis is used here

Topics: Programming SQL Mybatis Apache