1 the premise:

For details about the initialization process of Mybatis, see initialization of Mybatis.

MyBatis after the initialization, org. Apache. Ibatis. Session. The Configuration, will have lots of data has been initialized, for subsequent implementation:

1.1 mapperRegistry

An instance of MapperRegistry with an attribute Map

, MapperProxyFactory
> knownMappers:
>

  • Key: Mapper classes, such as interface ‘com. XXX. Yyy. Model. UserMapper’;
  • value: MapperProxyFactoryObject, which is a Mapper proxy classMapperProxyFactory, createMapperProxyObject executes methods defined in the Mapper class.

1.2 mappedStatements

Map

:
,>

  • key: MappedStatementObject id, such as’ com. XXX. Yyy. Model. UserMapper. SelectList ‘;
  • value: MappedStatementObject.

2 Query Process

MyBatis is divided into three steps to perform the query process:

  • 1 Create a SqlSession. The default implementation class isDefaultSqlSession;
  • 1 Obtain the Mapper, for examplesession.getMapper(UserMapper.class);
  • 2 Use Mapper to query informationuserMapper.findList().

2.1 create a SqlSession

DefaultSqlSessionFactory.openSession:

  public SqlSession openSession(boolean autoCommit) {
    / / the first step
    // In Configuration: defaultExecutorType = executorType.simple
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
  }
  
    / / the second step
    / / openSessionFromDataSource key code:
    final Executor executor = configuration.newExecutor(tx, execType);
    return new DefaultSqlSession(configuration, executor, autoCommit);
Copy the code

Executor in the Configuration. NewExecutor method created in:

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
Copy the code

NewExecutor is executed as follows:

  • Returns different executors depending on the ExecutorType;
  • Executortype. SIMPLE is available by default in ConfigurationSimpleExecutor;
  • Returns if caching is enabledCachingExecutorObject;
  • throughinterceptorChain.pluginAllAdd interceptorInterceptorList.

2.2 get Mapper

DefaultSqlSession from the Configuration. The mapperRegistry gets the realization of the Mapper object:

/ / the first step
// DefaultSqlSesison:
 public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }

/ / the second step
// Configuration:
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
  
/ / the third step
// MapperRegistry:
// Get the MapperProxyFactory object from knownMappers, execute newInstance to get the MapperProxy, the implementation object of the Mapper class.
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); }}/ / step 4
 MapperProxyFactory Creates a MapperProxy object
   protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

Copy the code

As you can see from the MapperProxyFactory code above, each execution of session.getMapper creates a MapperProxy object and its proxy object, so avoid calling session.getMapper multiple times.

2.3 Executing A Query

MyBatis uses JDK proxy mode, MapperProxy implements the InvocationHandler interface, so the implementation method of Mapper interface class is implemented in MapperProxy. Invoke method.

Invoke retrieves or creates a MapperMethod object and executes the mapperMethod.execute method.

//// Obtain the MapperMethod object
  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
  
/ / / / implement the execute
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
Copy the code

2.3.1 MapperMethod create

MapperMethod has two attributes:

  private final SqlCommand command;
  private final MethodSignature method;
Copy the code
1) SqlCommand

SqlCommand has two properties:

  • String name: ID of MappedStatement;
  • SqlCommandType Type: SqlCommandType of MappedStatement, UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH.

When SqlCommand is created, the MappedStatement object is obtained from the Configuration, and the assignment values for name and type are obtained:

String statementId = mapperInterface.getName() + "." + methodName;

if (configuration.hasStatement(statementId)) {
    return configuration.getMappedStatement(statementId);
}
Copy the code
2) MethodSignature
    private final boolean returnsMany; // configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray()
    private final boolean returnsMap; // 
    private final boolean returnsVoid; // void.class.equals(this.returnType)
    private final boolean returnsCursor; // org.apache.ibatis.cursor.Cursor.class.equals(this.returnType)
    private finalClass<? > returnType;//////
    private final ParamNameResolver paramNameResolver; // new ParamNameResolver(configuration, method)
Copy the code

2.3.2 MapperMethod. Execute

According to command-type, determine the operation to be performed:

 public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    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()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        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;
  }
Copy the code

In the executeFor* method of the execute call, the sqlSession.select* method is ultimately called.

2.3.3 session. SelectList

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      // First, extract the MappedStatement object from Configuration
      // statement : interfaceName + "." + methodName
      MappedStatement ms = configuration.getMappedStatement(statement);
      
      // Step 2, execute the query
      / / executor: front Configuration. NewExecutor create executor, SimpleExecutor by default
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
    } finally{ ErrorContext.instance().reset(); }}Copy the code

Executor.query call chain:

BaseExecutor.query -> BaseExecutor.queryFromDatabase -> SimpleExecutor.doQuery

SimpleExecutor.doQuery:

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();
      
      // Create the RoutingStatementHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      
      // Call statementhandler. prepare to create Statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      
      // Step 3 run statementhandler.query
      return handler.<E>query(stmt, resultHandler);
    } finally{ closeStatement(stmt); }}Copy the code

2.3.4 StatementHandler

1) RoutingStatementHandler

Configuration. NewStatementHandler RoutingStatementHandler is created in, and then set the interceptor:

  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

RoutingStatementHandler is a delegate pattern, according to MappedStatement statementType, return different StatementHandler implementation class:

  / / agent
  private final StatementHandler delegate;
  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: "+ ms.getStatementType()); }}Copy the code
2) StatementHandler.prepare

Create a java.sql.Statement object and call the chain:

RoutingStatementHandler.prepare -> BaseStatementHandler.prepare -> PreparedStatementHandler.instantiateStatement

InstantiateStatement is an abstract method of BaseStatementHandler that is instantiated by subclasses.

  // PreparedStatementHandler
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    if(mappedStatement.getResultSetType() ! =null) {
      return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      returnconnection.createStatement(); }}Copy the code

MyBatis- how to create a DataSource& get a Connection

3) StatementHandler.query

Once you have a Statement, you can use it to query:

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }
Copy the code