This is the 21st day of my participation in the More text Challenge. For more details, see more text Challenge

[TOC]

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

Basic understanding of Mybatis

A dynamic proxy

  • We saw earlier that Mapper is just an interface, not a logical implementation class. But interfaces can’t perform logic in Java. Here Mybatis is realized by dynamic proxy. About dynamic proxy we commonly use Jdk dynamic proxy and CGLIb dynamic proxy. The two are not to be repeated here. The CGLIB agent is widely used in the framework.

  • The thing about dynamic proxy is that all requests have one entry and are distributed through that entry. One use in development is load balancing.

  • The dynamic proxy for Mybatis uses a combination of the two.

  • Let’s look at the JDK and CGLIb implementations

The JDK implementation

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

public interface Developer {

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

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

Copy the code
  • Everyone has a different approach to these two skills. 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
  • The traditional way to do this is to create a JavaDeveloper object through the new mechanism provided by Java. With dynamic Proxy, the object is created to call the actual method through the java.lang.reflect.Proxy object.

  • The newProxyInstance method is used to obtain the interface object. This method takes three arguments

ClassLoader: Obtains the ClassLoader from the actual interface instance. Class<? >[] interfaces: InvocationHandler h: calls to methods on our interface object. At the calling 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. Analyze the problem 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 the interface in advance. The actual object he represents. This is very convenient for us to develop.

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 only need to implement the CGlib MethodInterceptor interface and load the instantiated 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 notice ======");
        returnobject; }}Copy the code
  • Let’s initialize cglib

public static void main(String[] args) {
    // The proxy class file is stored on the local disk so that we can decompile and view the source code
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/root/code");
    // Obtain the proxy object through the CGLIB dynamic proxy
    Enhancer enhancer = new Enhancer();
    // Set the superclass of the enhancer object
    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
  • A closer look at CGLIb is particularly similar to Spring’s AOP. The intercept control is carried out for the tangent point.

conclusion

  • By comparing the two dynamic proxies, it is easy to find that MyBatis implements Mapper invocation through JDK proxy. Our Mapper interface implementation executes the logic by proxy to the CORRESPONDING SQL in the XML

reflection

  • I believe that any Java engineer with some experience has at least some understanding of reflection. In fact, from the thought of which language is not used to have a reflex mechanism.
  • By reflection we’re getting rid of the object and we don’t have to call methods through the object anymore. You can get a method object from a Class object. The invoke method is invoked.

Configuration Function

  • The Configuration object stores all Mybatis configurations. The main thing is to initialize the parameters
    • properties
    • settings
    • typeAliases
    • typeHandler
    • ObjectFactory
    • plugins
    • environment
    • DatabaseIdProvider
    • Mapper Mapper

Mapper structure

  • BoundSql provides three main attributes: parameterMappings, parameterObject, and SQL

  • ParameterObject Specifies the parameter itself. We can pass parameters to the Java basic type, POJO, Map, or @param annotation.

  • When we pass myBatis, the Java primitive type is converted to int -> Integer

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

  • We pass multiple parameters without @param specifying the variable name, so parameterObject is similar

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

  • We pass multiple parameters and @param specifies the variable name, so parameterObject is similar

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

  • ParameterMapping records property, name, expression, javaType,jdbcType, and typeHandler information
  • The SQL attribute is an SQL in our mapper. Normally, we check SQL in common. The SQL does not need to be modified.

Sqlsession Execution Process (source tracing)

  • First, let’s look at how the Mapper interface we usually develop is dynamically proxyed. This is something that 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
  • By overloading the code and the above description of the JDK dynamic proxy. We can see that mapperProxy is the focus of our proxy.
  • MapperProxy is an InvocationHandler implementation class. 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
  • Discover through source code. Invoke internally first determines whether an object is a class. Finding the break point eventually leads to the cacheMapperMethod method to create the MapperMethod object.
  • Continuing to look at the Execute method in MapperMethod, we can see that the internal implementation is actually a command-line mode development. Different statements are executed by judging commands. Determine the specific execution statement and then pass the parameters to sqlSession to make the SQL call and get the result. Sqlsession is associated with normal JDBC development SQL. In the sqlsessionExecutor,StatementHandler,ParameterHandler,ResulthandlerFour major Kings

Executor

  • It is, by definition, an actuator. Submit the SQL provided by Java to the database. Mybatis provides three types of actuators.

  • NewExecutor source code in configuration. class

  • According to UML, it is easy to see that MyBatis provides three types of executor, namely 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 the configuration
      final Environment environment = configuration.getEnvironment();
      // Get the transaction factory in the 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 above source code we know that when SQLSession gets a database session object we either load an Executor object according to our Settings configuration. It’s also easy to configure in Settings

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

Copy the code
  • We can also set this up in Java code

factory.openSession(ExecutorType.BATCH);

Copy the code

StatementHandler

  • As the name suggests, StatementHandler is designed to handle database calls. The creation of this object is 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 uses a RoutingStatementHandler class for StatementHandler

  • With regard to the relationship between StatementHandler and RoutingStatementHandler, we can see from the source code that this is the same adapter pattern as Executor. The advantage of this pattern is that it makes it easier for us to proxy these objects. Here the reader can make a guess as to which dynamic proxy is used. Just to give you a hint, I’m using an interface here

  • When we look at the BaseStatementHandler structure we can see that it is exactly the same as the Executor. Mybatis also constructs a RoutingStatementHandler to load different concrete subclasses based on the configuration in setting. These subclasses inherit BaseStatementHandler.

  • In the previous section, we tracked Executor. We know that Mybatis defaults to SimpleExecutor. StatementHandler we tracked Mybaits by default PrePareStatementHandler. The source code for executing queries on SimpleExecutor is as follows

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

  • The logic of this method is to initialize the statement and set the connection timeout, among other ancillary functions
  • And then you set some parameters and things like that. Finally, we come to 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. The rest of it is easy to imagine that we are encapsulating the results of the data. As the code shows, it is resultSetHandler that does the work for us.

Result Handler (ResultSetHandler)


@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 from the tag configuration in the result XML.

conclusion

  • SqlSession will first query the cache through CacheExecutor when a query is opened. The StatementHandler is created through the SimpleExecutor subclass BaseExector after the cache breakdown. PrepareStatementHandler performs database operations based on PrepareStament. And return the result data through ResultSetHandler for the returned result

The theme