Mybatis source code mybatis agent stage

Keywords: Programming Database Mybatis Apache Java

When using Mybatis, you may have a question: why can you only write Mapper interface to operate database?

Its main idea is to use dynamic agent to generate implementation class, and then cooperate with SQL statement in xml Mapping file to access database. Let's take a look at the source code.

binding module core class

  • Mapper registry: the registry of MapperProxyFactory
  • MapperProxyFactory: the dynamic proxy factory class corresponding to mapper interface. Mapper interface and MapperProxyFactory factory class are one-to-one correspondence
  • MapperProxy: Mapper interface enhancer, through which the database access is realized
  • MapperMethod: for the wrapper class of insert, update, delete, select, flush node methods, it completes the operation on the database through sqlSession

Agent initialization

bindMapperForNamespace

In Mybatis source code (2), we will find that the last step when the configuration file is parsed is to call org.apache.ibatis.builder.xml.xmlmapperbuilder ා bindmapperfornamespace method. The main function of this method is to register the MapperProxyFactory of Mapper interface into MapperRegistry according to the namespace attribute. The source code is as follows:

private void bindMapperForNamespace() {
  // Get the namespace property (corresponding to the full class name of Mapper interface)
  String namespace = builderAssistant.getCurrentNamespace();
  if (namespace != null) {
    Class<?> boundType = null;
    try {
      boundType = Resources.classForName(namespace);
    } catch (ClassNotFoundException e) {
      //ignore, bound type is not required
    }
    if (boundType != null) {
      // Prevent duplicate loading
      if (!configuration.hasMapper(boundType)) {
        // Spring may not know the real resource name so we set a flag
        // to prevent loading again this resource from the mapper interface
        // look at MapperAnnotationBuilder#loadXmlResource
        configuration.addLoadedResource("namespace:" + namespace);
        // Register the dynamic proxy factory of Mapper interface in MapperRegistry
        configuration.addMapper(boundType);
      }
    }
  }
}
  1. Read the namespace property to get the full class name of Mapper interface
  2. Load Mapper interface into memory according to full class name
  3. Determine whether to load Mapper interface repeatedly
  4. Call the addMapper method of Mybatis configuration class to complete the following steps

addMapper

Org.apache.ibatis.session.configuration ා addmapper this method directly calls the org.apache.ibatis.binding.mapperregistry ා addmapper method to complete the registration.

public <T> void addMapper(Class<T> type) {
  // Must be an interface
  if (type.isInterface()) {
    if (hasMapper(type)) {
      // Prevent duplicate registration
      throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
    }
    boolean loadCompleted = false;
    try {
      // Create MapperProxyFactory proxy factory class according to the interface class
      knownMappers.put(type, new MapperProxyFactory<>(type));
      // It's important that the type is added before the parser is run
      // otherwise the binding may automatically be attempted by the
      // mapper parser. If the type is already known, it won't try.
      MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
      parser.parse();
      loadCompleted = true;
    } finally {
      // Remove Mapper in case of loading exception
      if (!loadCompleted) {
        knownMappers.remove(type);
      }
    }
  }
}
  1. Determine whether the loading type is an interface
  2. Duplicate registration verification. If the verification fails, a BindingException exception will be thrown.
  3. Create MapperProxyFactory proxy factory class according to the interface class
  4. Remove Mapper in case of loading exception

Get the corresponding Mapper object

In the quick start of Mybatis source code (1), you can get Mapper objects through sqlSession as follows:

PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);

getMapper get proxy object

sqlSession.getMapper(PersonMapper.class) finally calls org.apache.ibatis.binding.mapperregistry ා getmapper method, and finally returns the proxy object of the PersonMapper interface. The source code is as follows:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  // Get the corresponding agent factory according to the type
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  if (mapperProxyFactory == null) {
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  }
  try {
    // Create a new proxy object according to the factory class, and return
    return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}
  1. Get the corresponding agent factory according to the type
  2. Create a new proxy object according to the factory class, and return

newInstance create proxy object

Each Mapper interface corresponds to a MapperProxyFactory factory class. MapperProxyFactory creates proxy objects through JDK dynamic proxy. The proxy object of Mapper interface is method level, so new proxy objects need to be created every time the database is accessed. The source code is as follows:

protected T newInstance(MapperProxy<T> mapperProxy) {
  // Using JDK dynamic agent to generate agent instance
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}

public T newInstance(SqlSession sqlSession) {
  // Mapper's enhancer
  final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
}
  1. Get mapperproxy first
  2. Using JDK dynamic proxy to generate proxy objects according to the enhancer

Decompilation result of proxy class

import com.sun.proxy..Proxy8;
import com.xiaolyuh.domain.model.Person;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy8 extends Proxy implements Proxy8 {
    private static Method m3;
   ...

    public $Proxy8(InvocationHandler var1) throws  {
        super(var1);
    }

    ...
    public final Person selectByPrimaryKey(Long var1) throws  {
        try {
            return (Person)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    static {
        try {
            m3 = Class.forName("com.sun.proxy.$Proxy8").getMethod("selectByPrimaryKey", Class.forName("java.lang.Long")); 
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

From the decompilation results of the proxy class, we can directly call the invoke method of the enhancer to access the database.

Execution method

By decompiling the proxy object, we can find that all access to the database is implemented in the enhancer org. Apache. Ibatis. Binding. Mapperproxy ා invoke.

invoke

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    // If the method of Object itself is not enhanced
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    }
    // Determine whether it is the default method
    else if (method.isDefault()) {
      if (privateLookupInMethod == null) {
        return invokeDefaultMethodJava8(proxy, method, args);
      } else {
        return invokeDefaultMethodJava9(proxy, method, args);
      }
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
  // Get MapperMethod object from cache
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  // Execute MapperMethod
  return mapperMethod.execute(sqlSession, args);
}
  1. If the method of Object itself is not enhanced
  2. Determine whether it is the default method
  3. Get MapperMethod object from cache
  4. Execute MapperMethod

The agent phase of Mybatis is finished here, mapperMethod.execute(sqlSession, args); this code has already started to operate data, query data and result set mapping through sqlSession.

Flow chart

summary

Through the above source code analysis, it can be found that Mybatis interface oriented programming is implemented through the JDK dynamic agent mode. The main execution process is as follows:

  1. After the initialization of the mapping file, register the MapperProxyFactory class of the corresponding Mapper interface to MapperRegistry.
  2. Every time the database is operated, sqlSession obtains the agent class of Mapper interface through MapperProxyFactory
  3. The agent class calls MapperMethod, the encapsulation class of SQL node in XML Mapping file, to complete the operation of data through MapperProxy.

Posted by cyberplasma on Wed, 30 Oct 2019 00:06:53 -0700