This article is participating in “Java Theme Month – Java Debug Notes Event”, see < Event link > for more details.

[TOC]

Mybatis runs in two parts. The first part reads the Configuration file cached in the Configuration object. To create the SqlSessionFactory, the second part is the SqlSession execution process.

Basic knowledge of Mybatis

A dynamic proxy

  • We know that Mapper is just an interface, not a logical implementation class. But interfaces cannot execute logic in Java. Here Mybatis is implemented through dynamic proxy. Jdk dynamic proxy and Cglib dynamic proxy are commonly used about dynamic proxy. The two are not to be repeated here. The CGLIB proxy is widely used in frameworks.

  • The dynamic proxy is that all requests have an entry point from which they are distributed. One use in the development world is load balancing.

  • The dynamic proxy of Mybatis is a combination of the two.

  • Let’s look at the JDK and Cglib implementations

The JDK implementation

  • First we need to provide an interface, which is an abstraction for our programmers. Ability to code and fix bugs

public interface Developer {

    /** ** encoding */
    void code(a);

    /** * solve the problem */
    void debug(a);
}

Copy the code
  • Everyone handles these two skills differently. Here we need a concrete instance object

public class JavaDeveloper implements Developer {
    @Override
    public void code(a) {
        System.out.println("java code");
    }

    @Override
    public void debug(a) {
        System.out.println("java debug"); }}Copy the code
  • Our traditional invocation is to create a JavaDeveloper object through the New mechanism provided by Java. Dynamic Proxy calls the actual method by creating an object from the java.lang.reflect.proxy object.

  • Of the interface object via the newProxyInstance method. This method takes three arguments

ClassLoader: Obtains the ClassLoader Class<? >[] interfaces: our abstract interface InvocationHandler h: calls to methods on our interface object. At the invocation node we can do our business interception


JavaDeveloper jDeveloper = new JavaDeveloper();
Developer developer = (Developer) Proxy.newProxyInstance(jDeveloper.getClass().getClassLoader(), jDeveloper.getClass().getInterfaces(), (proxy, method, params) -> {
    if (method.getName().equals("code")) {
        System.out.println("I'm a special person. Code analyzes problems before code.");
        return method.invoke(jDeveloper, params);
    }
    if (method.getName().equals("debug")) {
        System.out.println("I don't have a bug.");

    }
    return null;
});
developer.code();
developer.debug();

Copy the code

CGLIB dynamic proxy

  • The advantage of cglib dynamic proxy is that it does not require us to prepare interfaces in advance. The actual object that he represents. This is very convenient for our development.

public class HelloService {
    public HelloService(a) {
        System.out.println("HelloService structure");
    }

    final public String sayHello(String name) {
        System.out.println("HelloService:sayOthers>>"+name);
        return null;
    }

    public void sayHello(a) {
        System.out.println("HelloService:sayHello"); }}Copy the code
  • We just need to implement cglib’s MethodInterceptor interface and load the instantiation object when we initialize cglib

public class MyMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("====== Insert pre-notification ======");
        Object object = methodProxy.invokeSuper(o, objects);
        System.out.println("====== insert the latter notification ======");
        returnobject; }}Copy the code
  • Let’s initialize cglib

public static void main(String[] args) {
    // The proxy class file is stored on the local disk to facilitate decomcompiling the source code
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/root/code");
    // Process of obtaining proxy objects through CGLIB dynamic proxy
    Enhancer enhancer = new Enhancer();
    // Set the enhancer object's parent class
    enhancer.setSuperclass(HelloService.class);
    // Set the enhancer callback object
    enhancer.setCallback(new MyMethodInterceptor());
    // Create a proxy object
    HelloService helloService = (HelloService) enhancer.create();
    // Call the target method through the proxy object
    helloService.sayHello();
}

Copy the code
  • Take a closer look at cglib and Spring AOP in particular. The section intercept control is carried out for the cut point.

conclusion

  • By comparing the two dynamic proxies, we can easily find that Mybatis is implemented through JDK proxy Mapper call. Our Mapper interface implements the corresponding SQL execution logic in XML by proxy

reflection

  • I’m sure any Java engineer with some experience knows something about reflection. In fact, from the thought of which language is not used to the mechanism of reflection.
  • By reflection we’re getting rid of objects and we don’t have to call methods through objects anymore. Method objects can be obtained through the Class object. The invoke method is invoked.

Configuration Object function

  • The Configuration object stores all the configurations of Mybatis. Initialize the parameters
    • properties
    • settings
    • typeAliases
    • typeHandler
    • ObjectFactory
    • plugins
    • environment
    • DatabaseIdProvider
    • Mapper Mapper

Mapper structure

  • BoundSql provides three main properties parameterMappings, parameterObject, and SQL

  • ParameterObject The parameter itself. We can pass arguments to Java primitives, POJO, Map, or @param annotations.

  • Mybatis will be converted to the corresponding wrapper object int -> Integer when we pass the Java primitive type mybatis

  • If we pass POJO, Map. It’s the object itself

  • ParameterObject is similar if we pass multiple parameters without @param specifying the variable name

{“1″:p1,”2″:p2,”param1″:p1,”param2”:p2}

  • ParameterObject is similar if we pass multiple arguments and @param specifies the variable name

{“key1″:p1,”key2″:p2,”param1″:p1,”param2”:p2}

  • ParameterMapping records attributes, names, expressions, javaType,jdbcType, and typeHandler
  • The SQL attribute is a piece of SQL in our mapper. Normally we verify SQL in common. Normally, SQL does not need to be modified.

Sqlsession Execution Process (source tracing)

  • First let’s take a look at how the Mapper interface we usually develop dynamically proxies. This needs to be mentionedMapperProxyFactoryThis class. In the classnewInstancemethods

protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

Copy the code
  • Through the full code above and the above expression of the JDK dynamic proxy. We can know that mapperProxy is the focus of our proxy.
  • MapperProxy is an implementation class of InvocationHandler. The invoke method he overrides is the method entry that the proxy object executes.

@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)) {
    returninvokeDefaultMethod(proxy, method, args); }}catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}

Copy the code

private boolean isDefaultMethod(Method method) {
return (method.getModifiers()
    & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
    && method.getDeclaringClass().isInterface();
}

Copy the code
  • Discovery through source code. Invoke internally determines whether an object is a class. Breaking point discovery eventually leads you to the cacheMapperMethod method to create the MapperMethod object.
  • Moving on to the Execute method in MapperMethod, we can see that the internal implementation is actually command-line development. Execute different statements by judging commands. Determine the specific execution statement and pass the parameters to sqlSession for SQL calls and results. Sqlsession is associated with normal JDBC development SQL. In the sqlsessionExecutor,StatementHandler,ParameterHandler,ResulthandlerFour major Kings

Executor

  • He’s an actuator, as the name suggests. Commit the SQL provided by Java to the database. Mybatis provides three actuators.

  • Configuration. Class newExecutor source code

  • Mybatis provides three types of executor: SimpleExecutor, ReuseExecutor and BatchExecutor

public SqlSession openSession(a) {
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null.false);
}

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      // Get the environment in configuration
      final Environment environment = configuration.getEnvironment();
      // Get the transaction factory in configuration
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // Get the actuator
      final Executor executor = configuration.newExecutor(tx, execType);
      // Return the default SqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
    } finally{ ErrorContext.instance().reset(); }}Copy the code
  • From the source code above we know that when sqlSession gets a database session object we can either load an Executor object based on our Settings configuration. Configuration in Settings is also simple

<settings>
<! -- SIMPLE, REUSE, BATCH -->
	<setting name="defaultExecutorType" value="SIMPLE"/>
</settings>

Copy the code
  • We can also set it in Java code

factory.openSession(ExecutorType.BATCH);

Copy the code

StatementHandler

  • As the name suggests, StatementHandler handles database calls. The creation of this object is still managed in Configuration.

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

Copy the code
  • Mybatis StatementHandler uses the RoutingStatementHandler class

  • The relationship between StatementHandler and RoutingStatementHandler can be seen from the source code that this is the same adapter pattern as Executor. The advantage of using this pattern is that we can easily proxy these objects. Here the reader can guess what kind of dynamic proxy is used. I’ll give you a hint that I’m using an interface here

  • When we look at the BaseStatementHandler structure we see exactly the same as Executor. Similarly, Mybatis constructs the RoutingStatementHandler to load specific subclasses according to the Settings. These subclasses inherit from BaseStatementHandler.

  • In the previous section we tracked executors. We know that Mybatis defaults to SimpleExecutor. StatementHandler we’re tracking Mybaits and the default is PrePareStatementHandler. The source code for executing queries in SimpleExecutor is as follows

  • We found that querying for money in executor first causes the statementHandler to build a Statement object. The final result is the prepare method in StatementHandler. This method is encapsulated in the abstract class BaseStatmentHandler.

  • The logic of this method is to initialize the statement and set the connection timeout and other auxiliary functions
  • Then it’s about setting some parameters and so on. Finally, we come to the executor’s DoQuery

  • PrepareStatement is a common class in our JDBC development. Before executing this method, we need to set the SQL statement and set the parameters to compile. This is just a series of steps. We say the process is also PrepareStatementHandler prepareStatement help us to do. So the rest of it we can easily imagine is our encapsulation of the data results. As the code shows, resultSetHandler dismounts and does the work for us.

Result handler (Results Handler)


@Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while(rsw ! =null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if(resultSets ! =null) {
      while(rsw ! =null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if(parentMapping ! =null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; }}return collapseSingleResultList(multipleResults);
  }

Copy the code
  • This method can be exported as an encapsulation of the result in the tag configuration in the result XML.

conclusion

  • SqlSession starts a query by first querying the cache through the CacheExecutor. The StatementHandler will be created from SimpleExecutor of the BaseExector subclass after the cache breakdown. PrepareStatementHandler performs database operations based on PrepareStament. The result data is returned using the Resultsetthandler

The theme