Mybatis framework source code notes (4) - Process analysis of Mybatis execution of addition, deletion, modification and query methods

1 Overview of Mybatis process analysis

The process of performing damage modification in the Mybatis framework is basically the same. It is very simple. As long as you write a test demo and look at the source code, you can basically understand what is going on. The query operation is slightly different. Here we mainly analyze it through the query operation. Process design and implementation of the entire framework.

2 Basic process of Mybatis query operation

2.1 Creation process of Mapper proxy object MapperProxy

Insert image description here
Mapper (actually a proxy object) is obtained from SqlSession, because there is a Configuration class object in the DefaultSqlSession class of SqlSession implementation class, and after we complete the operation of creating the sqlSession object, the configuration object has been stored The mapping relationship between all mappers and their corresponding proxy objects, as shown below:
Insert image description here
Insert image description here
Take a look at mapperRegistry as shown below:
Insert image description here
Creating proxy objects in the MapperRegistry class The core steps are as follows
Insert image description here
Calling the newInstance method in the MapperProxyFactory class
Insert image description here
Here, the proxy class object of the target mapper layer is created through a dynamic proxy and returned.

2.2 The calling process of creating Mapper proxy object MapperProxy

Summarizing the calling logic of the getMapper(Class clazz) method to create a dynamic proxy object is as follows:
Insert image description here

2.3 The core process of adding, deleting, modifying and checking dynamic proxy objects

Here we use the query interface to analyze the process

At the application layer, we will call the following line of code to implement the query. Now let's analyze how this line of code is executed in the source code of Mybatis, and how it implements the function of querying the data we want.

List<LibBook> libBooks = mapper.selectBooksByCondition("T311.5/3-1", null, null, null);

Everyone must be familiar with dynamic proxies. If you want to look at the source code, you don’t even have this basic knowledge. It is recommended not to look at it. After you have a solid foundation in Java, you can study the source code. When we use Proxy to create dynamic objects, For example, you need to pass an implementation class of the InvocationHandler interface or an anonymous inner class object.
When we create a dynamic proxy class in Chapter 2.1, in the newInstance method in the MapperProxyFactory class, the InvocationHandler we pass is The implementation class is our MapperProxy class object, so this MapperProxy class must implement the InvocationHandler interface. Let's verify whether it is:

Sure enough, the MapperProxy class implements the InvocationHandler interface and implements the invoke method
Insert image description here
Insert image description here
Next we call the method selectBooksByCondition in the Mapper interface, which is actually executed through the MapperProxy.invoke() method. .

2.3.1 MapperProxy.invoke() method call analysis

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
    try {
    
    
      /**
       *  这里给大家解释一下这个invoke判断里面加这个判断的原因:
       *      大家都知道Object对象默认已经实现了很多方法, 我们的Mapper接口在进行定义的时候, 可能定义了静态方法、 默认方法以及抽象方法
       *      因此在创建了动态代理对象的时候, 这个动态代理类肯定也包含了很多的方法, 从Object类继承的方法, 从接口继承的默认方法,
       *      以及从接口继承抽象方法需要实现取执行SQL语句的方法
       *
       *      这个if分值判断的只要目的在将无需走SQL执行流程的方法如(toString/equals/hashCode)等先过滤掉
       *      然后再抽象方法及默认方法中通过一个接口MapperMethodInvoker再进行一次判断,找到所有需要执行SQL的方法通过PlainMethodInvoker的invoke
       *      方法取执行SQL语句获取结果,能够加快获取 mapperMethod 的效率
       */

      if (Object.class.equals(method.getDeclaringClass())) {
    
    
        return method.invoke(this, args);
      } else {
    
    
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
    
    
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

Insert image description here
The PlainMethodInvoker().invoke() method will call the execute() method in the MapperMethod class
Insert image description here

2.3.2 MapperMethod.execute() method call analysis

The specific execution logic of the execute() method in the MapperMethod class is as follows
Insert image description here

 public Object execute(SqlSession sqlSession, Object[] args) {
    
    
    Object result;
    switch (command.getType()) {
    
    
      // 插入操作
      case INSERT: {
    
    
          // 如果你用过Mybatis的话, 你一定清楚, Mybatis的中参数传递的方式有以下几种
          	// 第一种: [arg0,arg1,...]
          	// 第二种: [param1,param2,...]
          	//  convertArgsToSqlCommandParam(args)方法就是实现这种映射
	       // 举例说明:我之前传递的参数是一个Book对象{"bookId": "val1", "bookIndexNo":"val2", "bookName":"val3"}
	       // 经过convertArgsToSqlCommandParam()方法处理之后得到{"bookId": "val1", "bookIndexNo":"val2", "bookName":"val3", "param1": "val1", "param2":"val2", "param3":"val3"}
        Object param = method.convertArgsToSqlCommandParam(args);
        // 执行SQL操作并对返回结果进行封装处理
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      // 更新操作
      case UPDATE: {
    
    
        Object param = method.convertArgsToSqlCommandParam(args);
        // 执行SQL操作并对返回结果进行封装处理
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
       // 删除操作
      case DELETE: {
    
    
        Object param = method.convertArgsToSqlCommandParam(args);
        // 执行SQL操作并对返回结果进行封装处理
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
      	// 查询操作的情况比较复杂, 需要分情况逐一处理
        if (method.returnsVoid() && method.hasResultHandler()) {
    
    
          // 如果查询操作的返回为空并且方法已经指定了结果处理器时,执行以下语句
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
    
    
          // 如果查询操作的返回返回结果为List集合, 执行以下语句
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
    
    
          // 如果查询操作的返回返回结果为Map集合, 执行以下语句
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
    
    
          // 如果查询操作的返回返回结果为Cursor, 执行以下语句
          result = executeForCursor(sqlSession, args);
        } else {
    
    
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
           // 如果查询操作的返回返回结果为单个对象并且指定返回类型为Optional, 则将返回结果封装成Optional对象返回
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
    
    
            result = Optional.ofNullable(result);
          }
        }
        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;
  }

The query statement we are currently executing returns a list of results, so executeForMany(sqlSession, args); is called.
Insert image description here

	private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    
    
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
	// 这里是判断你的mapper层接口方法是否传递了RowBounds对象,根据是否传递了RowBounds对象来调用sqlSession对象的selectList的不同传参的重载方法
	// 这个RowBounds对象主要是来进行分页功能,会将所有符合条件的数据全都查询出来加载到内存中,然后在内存中再对数据进行分页(当数据量非常大的时候, 就会发生OOM, 一般不推荐使用)
    if (method.hasRowBounds()) {
    
    
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
    
    
	  // 我们这里没有传递RowBounds对象, 所以调用的是这个方法
      result = sqlSession.selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
    
    
      if (method.getReturnType().isArray()) {
    
    
        return convertToArray(result);
      } else {
    
    
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

2.3.3 Analysis of the logical flow of query execution by executor.query() executor

2.3.3.1 Create PreparedStatement object

When we created the configuration class object, we have set the default executor object type to Simple
Insert image description here

So the executor here should be a SimpleExecutor object, but in fact it is not. The executor object here is a CachingExecutor object. You must have questions here. During the debugging process, I clearly did not see a CachingExecutor object being created. This object is When was it created? Why is it not a SimpleExecutor object?

Here is the answer. The following two lines of code will definitely be familiar to you as long as you have used the Mybatis framework.

SqlSessionFactory factory = factoryBuilder.build(ins);
SqlSession sqlSession = factory.openSession(true);

factory.openSession(true) This method calls us to follow up and see

Insert image description hereInsert image description here
Okay, the above is when the executor was created and why it should be a simpleExecutor, but it is actually a cacheExecutor object.

Next, let’s talk about the execution process of the query method.

The sqlsession object will call the query method of the executor to start the query
Insert image description here
Here the query() method of the delegate SimpleExecutor object is called, but the SimpleExecutor object does not have a query method, so the parent class is called The query() method in BaseExecutor
Insert image description here
The query() method calls the queryFromDatabase() method in the parent class BaseExecutor
Insert image description here
The queryFromDatabase()() method calls it again The abstract method doQuery() method of the parent class BaseExecutor is not implemented in the parent class, but the subclass SimpleExecutor rewrites this method and calls the doQuery() method in the subclass SimpleExecutor to execute
Insert image description here
The doQuery() method in the subclass SimpleExecutor is as shown below:
Insert image description here
In the doQuery() method in the SimpleExecutor classStatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); when this line of code is executed, the current The ParameterHandler object and ResultSetHandler object of the proxy method will also be created and expanded. If we customize ParameterHandler, ResultSetHandler, and StatementHandler in the configuration file, they will be registered through the interceptor and called in sequence during this process.
Insert image description here

There is no implementation in RoutingStatementHandler and it is used to create basic StatementHandler. The type of StatementHandler will be determined based on the statementType in MappedStatement (there are three types in total: STATEMENT, PREPARED, CALLABLE). The MappedStatement object defaults to PREPARED.
Insert image description here

In the doQuery() method in the SimpleExecutor classStatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);The following method in the Configuration class will be called

/** 拦截参数处理器:通过拦截器拓展插件功能对ParameterHandler的基础功能进行增强 */
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    
    
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

/** 拦截结果映射处理器:通过拦截器拓展插件功能对ResultSetHandler的基础功能进行增强 */
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
      ResultHandler resultHandler, BoundSql boundSql) {
    
    
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

/** 拦截SQL语句处理器:通过拦截器拓展插件功能对StatementHandler的基础功能进行增强 */
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;
  }

After the RoutingStatementHandler object is created, the prepareStatement() method is called to create the Statement object.
Insert image description here

2.3.3.2 Parameter processing in Statement

After creating the Statement object that will eventually be executed in the SimpleExecutor object, it is time to process the actual replacement of the placeholders in the SQL statement into the database, which is mainly achieved through statementHandler.parameterize(stmt). Specifically Still call the .parameterize(stmt) method in the PreparedStatementHandler class to process
Insert image description here
Our ParameterHandler object is used here, and parameter replacement is completed through the setParameters() method.
If you are interested in the specific method implementation, you can study it yourself

2.3.3.3 ResultSet results and processing

The handler.query(stmt, resultHandler) method of the SimpleExecutor class will eventually call the query method of PreparedStatementHandler to execute the SQL statement and return the result
Insert image description here
The query method of PreparedStatementHandler will call PreparedStatement.execute() Execute the SQL statement and return the query result set
Insert image description here
The DefaultResultSetHandler.handleResultSets() method will process the query result set into a List object in Java and return it.

3 Query method calling logic diagram

Insert image description here

4 Notes during source code reading

MapperProxy
	invoke

MapperMethod
	execute()
	executeForMany()


DefaultSqlSession
	selectList()

CachingExecutor
	query()
		1、生成二级缓存KEY
	createCacheKey()
		1、将缓存KEY作为参数传递,调用重载query()方法
	query()
		1、从MappedStatement对象中获取Cache对象
		2、如果Cache对象不是null, 说明有二级缓存
			2.1、判断MappedStatement对象的isFlushCacheRequired属性是为true, 如果为true,刷新缓存
			2.2、判断MappedStatement对象是否使用缓存(isUseCache属性是否为true),并且resultHandler属性默认为null, 如果两者都满足, 直接从Cache对象中通过缓存KEY查询结果数据list
			2.3、判断结果数据list是否为空,如果为空,从数据库中查询出结果,然后将查询结果放置到Cache对象中的缓存KEY下,最后把查询结果list返回
		3、如果Cache对象是null, 直接从数据库中查询出结果(调用BaseExecutor.query())
		
BaseExecutor
	createCacheKey()
	query()
		1、判断当前的queryStack是否为0,并且MappedStatement对象的isFlushCacheRequired属性是为true,满足条件就清空一级缓存,否则跳过
		2、判断MappedStatement对象的resultHandler属性是否为null,满足条件就直接从一级缓存中通过缓存KEY查询结果数据list
			2.1、如果结果数据list不为空,调用handleLocallyCachedOutputParameters()方法对结果进行处理(主要是因为如果调用的是存储过程,执行的结果需要接受并处理)
			2.2、如果结果数据list为空,调用queryFromDatabase()直接从数据库中进行查询结果
		3、处理嵌套查询
		    if (queryStack == 0) {
    
    
			  for (DeferredLoad deferredLoad : deferredLoads) {
    
    
				deferredLoad.load();
			  }
			  // issue #601
			  deferredLoads.clear();
			  // 嵌套查询会借助一级缓存,所以一级缓存不能关闭
			  // 嵌套查询是肯定会延迟加载的,存入DeferredLoad类,避免重复的查询执行
              // 执行嵌套查询时,当有结果值就直接存入,没有就存入一个占位符,这样相同的嵌套查询,在一级缓存中只会存在一个,当所有的都处理完成以后,然后再最终处理所有的延迟加载
		4、返回最终结果

	queryFromDatabase()
		1、首先通过缓存KEY在缓存对象中先开辟空间,因为缓存结果还没有从数据库中查询,先设置一个占位符告诉其他人这个坑已经有人占了
		2、调用doQuery()方法查询数据
		3、将第一步的缓存对象清除(因为它没有真正的保存数据对象,只是在我查询数据还没有返回数据结果的这段时间假装已经有缓存了)
		4、将真正从数据中查询出来的数据对象放入一级缓存,然后然后结果list
SimpleExecutor
	doQuery()
		1、创建StatementHandler对象
			1.1 MappedStatement对象中默认的statementType对象是"PREPARED", 这里会执行new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
			1.2 new PreparedStatementHandler的构造方法中调用了父类BaseStatementHandler的构造方法
			1.3 父类BaseStatementHandler的构造方法中做了parameterHandler和resultSetHandler的初始化操作,调用了configuration.newParameterHandler()和configuration.newResultSetHandler()
			方法进行对象创建,这里默认都是创建的DefaultParameterHandlerDefaultResultSetHandler对象
		2prepareStatement() 
			2.1 创建Connection连接对象
			2.2 执行statementHandler对象的prepare()方法,返回Statement对象
				// 2.2.1 调用RoutingStatementHandler类下的prepare()方法,返回Statement对象
				// 2.2.2 调用BaseStatementHandler类下的prepare()方法,返回Statement对象
			2.3 执行statementHandler对象的parameterize()方法
				// 2.3.1 调用RoutingStatementHandler类下的parameterize()方法
				// 2.3.2 调用PreparedStatementHandler类下的parameterize()方法
			2.4 返回Statement对象
		3、调用StatementHandlerquery()方法
			3.1 调用RoutingStatementHandler类的query()方法
			3.2 调用PreparedStatementHandler类的query()方法

BaseStatementHandler
	这个方法在创建
	prepare()
		1、调用instantiateStatement()创建Statement对象
			1.1 调用PreparedStatementHandler类下的instantiateStatement()方法创建Statement对象
		2、调用setStatementTimeout()设置执行和事务的超时时间
		3、调用setFetchSize()设置驱动的结果集获取数量(fetchSize)
		4、最后返回Statement对象

PreparedStatementHandler
	instantiateStatement()
		1、调用JDBC的connection.prepareStatement(sql)方法创建出一个PreparedStatement对象返回
	parameterize()
		1、调用parameterHandler对象的setParameters()方法将PrepareStatement对象中的SQL语句中的参数占位符都替换成传入的参数值
			1.1 调用DefaultParameterHandler类的setParameters()方法
			1.2 遍历所有的ParameterMapping对象,依次替换所有的占位符为实际的传入的参数值
	query()
		1、获取到PreparedStatement对象
		2、调用PreparedStatementexecute()执行SQL语句
		3、调用DefaultResultSetHandler类的handleResultSets()方法处理查询结果集

DefaultResultSetHandler
	handleResultSets()
			handleResultSet()
				handleRowValues()
					handleRowValuesForSimpleResultMap
						getRowValue()
							applyPropertyMappings()方法完成从ResultSet结果集中的每一个行数据和Java对象之间的映射
						storeObject()getRowValue()处理完成的Objcet数据添加到List集合





	

Guess you like

Origin blog.csdn.net/qq_41865652/article/details/128108612