Mybatis source code analysis - operating principle and process

You can refer to the article in the WeChat official account, the format is relatively clear, the link: http://mp.weixin.qq.com/s/eZpFfLtpJ4zE24HLYliUyA
Welcome to WeChat official account

1. Configure the configuration file, scan the mapper file 
<bean id="sqlSessionFactory " class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation" value="classpath:XXX.xml"/>
</bean>


2 , Configure SqlSessionFactoryBean (create the factory class of mybatis), the configuration of dataSource is not posted here 
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
     <property name="basePackage" value="XXX.mapper" />
     <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
The afterPropertiesSet method of the SqlSessionFactoryBean class has all properties set before execution. Its function is to verify the dataSource (configured in the configuration file) and the sqlSessionFactoryBuilder (a new object created by default), and then create the sqlSessionFactory (more on this later).
When the service is started, all mappedStatements will be loaded into the sqlSession 

3. Register the scanned mapper interfaces one by one as proxies. The calling process is as follows: MapperFactoryBean.getObject() --> SqlSessionTemplate.getMapper(Class<T> type ) --> Configuration.getMapper(Class<T> type, SqlSession sqlSession) --> MapperRegistry.getMapper(Class<T> type, SqlSession sqlSession)  
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  if (!knownMappers.contains(type))
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  try {
    return MapperProxy.newMapperProxy(type, sqlSession);

    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}
MapperProxy class proxy code: MapperProxy.newMapperProxy(type, sqlSession);`
public static <T> T newMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {
  ClassLoader classLoader = mapperInterface.getClassLoader();
  Class<?>[] interfaces = new Class[]{mapperInterface};
  MapperProxy proxy = new MapperProxy(sqlSession);
  return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
}
After all Mapper interfaces are proxied, subsequent calls to methods in the interface will enter MapperProxy.invoke() to execute the corresponding logic.

4. After the service is started, when the mapper interface is called, MapperProxy will automatically proxy and call the invoke method (because when the service is started, all Mapper interfaces are set to MapperProxy proxy) 
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  if (method.getDeclaringClass() == Object.class) {
    return method.invoke(this, args);
  }
  //Through proxy classes and methods Name (full path method name) Get Mapper interface class
  final Class<?> declaringInterface = findDeclaringInterface(proxy, method);
  //Set statement, sqlSession, declaringInterface, etc. to prepare for executing sql
  final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method , sqlSession);
  //Execute sql and return the result 
  final Object result = mapperMethod.execute(args);
  if (result == null && method.getReturnType().isPrimitive() && !method.getReturnType().equals(Void .TYPE)) {
    throw new BindingException("Mapper method '" + method.getName() + "' (" + method.getDeclaringClass() + ") attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
  }
  return result;
}
The execute method in the MapperMethod class, the specific execution operation class; if it returns a list, it corresponds to returnsMany, and if it returns a Map, it corresponds to returnsMap 
public Object execute(Object[ ] args) {
  Object result = null;
  if (SqlCommandType.INSERT == type) {
    Object param = getParam(args);
    result = sqlSession.insert(commandName, param);
  } else if (SqlCommandType.UPDATE == type) {
    Object param = getParam(args);
    result = sqlSession.update(commandName, param);
  } else if (SqlCommandType.DELETE == type) {
    Object param = getParam(args);
    result = sqlSession.delete(commandName, param);
  } else if (SqlCommandType.SELECT == type) {
    if (returnsVoid && resultHandlerIndex != null) {
      executeWithResultHandler(args);
    } else if (returnsMany) {
      result = executeForMany(args);
    } else if (returnsMap) {
      result = executeForMap(args);
    } else {
      Object param = getParam(args);
      result = sqlSession.selectOne(commandName, param);
    }
  } else {
    throw new BindingException("Unknown execution method for: " + commandName);
  }
  return result;
}
Finally, the specific execution of the sql statement is to call the methods in sqlSession: sqlSession.insert, sqlSession.update, sqlSession.delete, sqlSession.select, sqlSession.<E>selectList, sqlSession.selectOne, etc.

5. sqlSession executes the specific sql operation process 
. When executing sqlSession.<E>selectList, you need to call the method in the SqlSession implementation class (SqlSessionTemplate), pay attention to the following code, which is to call selectList through sqlSessionProxy, indicating that a Proxy 
public <E> List<E> selectList(String statement, Object parameter) {
  return this.sqlSessionProxy.<E> selectList(statement, parameter);
}
After entering the SqlSessionTemplate class, this class has a constructor
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory , ExecutorType executorType,
    PersistenceExceptionTranslator exceptionTranslator) {

  notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
  notNull(executorType, "Property 'executorType' is required");

  this.sqlSessionFactory = sqlSessionFactory;
  this.executorType = executorType;
  this.exceptionTranslator = exceptionTranslator;
  this.sqlSessionProxy = (SqlSession) newProxyInstance(
      SqlSessionFactory.class.getClassLoader(),
      new Class[] { SqlSession.class },
      new SqlSessionInterceptor());
}
Looking at the last sentence of code, sqlSessionProxy creates a new proxy instance and executes the methods in the specific sqlSessionProxy ( sqlSessionProxy.<E> selectList), enter the SqlSessionInterceptor.invoke() method  
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   //获取一个sqlSession 
  final SqlSession sqlSession = getSqlSession(
      SqlSessionTemplate.this.sqlSessionFactory,
      SqlSessionTemplate.this.executorType,
      SqlSessionTemplate.this.exceptionTranslator);
  try {
    //真正执行selectList方法     
    Object result = method.invoke(sqlSession, args);
    if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
      // force commit even on non-dirty sessions because some databases require
      // a commit/rollback before calling close()
      sqlSession.commit(true);
    }
    return result;
  } catch (Throwable t) {
    Throwable unwrapped = unwrapThrowable(t);
    if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
      Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
      if (translated != null) {
        unwrapped = translated;
      }
    }
    throw unwrapped;
  } finally {
    //After the execution is completed, you need to close the SqlSession   
    closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
  }
}
In the invoke method, you first need to get a SqlSession, through sessionFactory.openSession(executorType ), the code is in the SqlSessionUtils class
SqlSession session = sessionFactory.openSession(executorType);
debug到上面的Object result = method.invoke(sqlSession, args);后,继续往下运行,就进入了DefaultSqlSession中的selectList中 
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    MappedStatement ms = configuration.getMappedStatement(statement);
    List<E> result = executor.<E>query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    return result;
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}
Here the code `executor.<E>query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);` continues to execute. If there is a Plugin configured in the configuration file, it will enter the plug-in class you configured, and before entering the configured plug-in class, it will go to the proxy tool class Plugin (org.apache.ibatis.plugin.Plugin.java)  
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    Set<Method> methods = signatureMap.get(method.getDeclaringClass());
    if (methods != null && methods.contains(method)) {
      return interceptor.intercept(new Invocation(target, method, args));
    }
    return method.invoke(target, args);
  } catch (Exception e) {
    throw ExceptionUtil.unwrapThrowable(e);
  }
}
Then enter the plug-in class you configured through interceptor.intercept(new Invocation(target, method, args)), and execute `executor.<E>query()` until the configured plug-in class code is executed. method, continue to run at this time, then enter the query() method in BaseExecutor
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  ErrorContext. instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
  if (closed) throw new ExecutorException("Executor was closed.");
  if (queryStack == 0 && ms.isFlushCacheRequired()) {
    clearLocalCache();
  }
  List<E> list;
  try {
    queryStack++;
    list = resultHandler == null ? (List<E> ) localCache.getObject(key) : null;
    if (list != null) {
      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    } else {
      list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
  } finally {
    queryStack--;
  }
  if (queryStack == 0) {
    for (DeferredLoad deferredLoad : deferredLoads) {
      deferredLoad.load();
    }
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      clearLocalCache(); // issue #482
    }
  }
  return list;
}
Note that the result you want will be obtained from the local cache first. The value of this key corresponds to the namespace+statementId+sql statement. If there is no cache (need to enable the cache function, the result will be cached locally) In the cache), then go to query the database queryFromDatabase()
in queryFromDatabase() in doQuery() to query, jump to the doQuery() in the SimpleExecutor class 
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds , ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Statement stmt = null;
  try {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
    stmt = prepareStatement( handler, ms.getStatementLog());
    return handler.<E>query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}
When handler.<E>query is executed, it will be delegated to PreparedStatementHandler to execute query() 
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = ( PreparedStatement) statement;
  //Execute the query operation
  ps.execute();
  return resultSetHandler.<E> handleResultSets(ps);
}
When executing the operation ps.execute(), enter the execute() method in PreparedStatement, which has Sentence code
rs = executeInternal(this.maxRows, sendPacket, doStreaming, (this.firstCharOfStmt == 'S'),
      metadataFromCache, false);
After entering the executeInternal method, the specific execution code is
rs = locallyScopedConnection.execSQL(this, null, maxRowsToRetrieve, sendPacket,
   this.resultSetType, this.resultSetConcurrency,
   createStreamingResultSet, this.currentCatalog,
   metadataFromCache, isBatch);
continue to execute this code, enter the execSQL method in the ConnectionImpl class, return the result of the execution in this method
return this.io.sqlQueryDirect(callingStatement , null, null,
      packet, maxRows, resultSetType,
      resultSetConcurrency, streamResults, catalog,
      cachedMetadata);
then enter the sqlQueryDirect method in the MysqlIO class, the following code is to send the execution statement
Buffer resultPacket = sendCommand(MysqlDefs.QUERY, null, queryPacket,
      false , null, 0);
continue down to the sendCommand method
send(this.sendPacket, this.sendPacket.getPosition());
enter the send() method, and finally connect to the database through the socket, and send the statement to be executed to the database server , let it execute, then return the result to mysqlOutput, and finally backfill the result back.

At this point, the mybatis running process is basically accepted.

6. Summary 
Through the analysis of the source code of mybatis, it can be seen that dynamic agents are used in many places in the whole process. The registration Mapper interface uses the agent, and the agent is also used when performing the specific operations in the sqlSession, which is also useful in other places. Agent: From the source code analysis of Mybatis, it is not difficult to understand its entire operation process. Only after understanding its principle can it be handy in subsequent use.

If there is something I don't understand, I hope you guys can correct me

Guess you like

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