BatchExecutor of Executor source code parsing in MyBatis not understood

To facilitate source code analysis, let's start with MyBatis Demo

mybatis-mysql-config.xml

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
 3         "http://mybatis.org/dtd/mybatis-3-config.dtd">
 4 
 5 <configuration>
 6     <properties>
 7         <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
 8         <property name="url" value="jdbc:mysql://127.0.0.1:3306/gys?serverTimezone=UTC"/>
 9     </properties>
10     <settings>
11         <setting name="defaultExecutorType" value="SIMPLE"/>
12     </settings>
13     <!--Environment configuration, connected database, which is used here MySQL-->
14     <environments default="dev">
15         <environment id="dev">
16             <!--Specify the type of transaction management, which is easy to use here Java Of JDBC Commit and rollback settings for-->
17             <transactionManager type="JDBC" />
18             <!--dataSource Refers to the connection source configuration, POOLED yes JDBC The implementation of data source connection pool of connection object-->
19             <dataSource type="POOLED">
20                 <property name="driver" value="${driver}"></property>
21                 <property name="url" value="${url}"></property>
22                 <property name="username" value="root"></property>
23                 <property name="password" value="gys"></property>
24             </dataSource>
25         </environment>       
26     </environments>
27     <mappers>   
28         <mapper resource="mapper/user.xml"></mapper>
29     </mappers>
30 </configuration>

user.xml

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 3         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 4 <mapper namespace="dao.IUserMapper">  
 5 
 6     <insert id="insertUser" parameterType="model.User">
 7         insert into user
 8         (name,age)
 9         values
10         (#{name},#{age})
11     </insert>
12 
13 </mapper>

Entry method main:

 1     public static void main(String[] args) throws Exception {
 2         SqlSessionFactory sqlSessionFactory1=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-mysql-config.xml"),"dev");
 3         SqlSession sqlSession1= sqlSessionFactory1.openSession(true);
 4         IUserMapper userMapper=sqlSession1.getMapper(IUserMapper.class);
 5         User user=new User();
 6         user.setAge(28);
 7         user.setName("a");
 8         int i=userMapper.insertUser(user);
 9         System.out.println("Rows affected"+i);
10         sqlSession1.close();
11     }

This Executor code has a long encapsulation from the above Demo execution code. If you analyze the Executor, you need to analyze this long encapsulation code;

How can this source code start to make people feel that it's natural and natural?

Come on, let's go step by step;

Step 1: how to get the defaultExecutorType configuration during the build process

 

Line 75 instantiates an XMLConfigBuilder object, which is an xml parser.

Line 78 calls the build method in line 91. The parameter of this method is a Configuration object. Then the parser.parse() method must return a Configuration object;

In other words, the configuration file read in parser.parse() and assigned to the configuration object.

parser.parse() source code:

parseConfiguration() source code

 

 1 private void parseConfiguration(XNode root) {
 2       try {     
 3         propertiesElement(root.evalNode("properties"));
 4         Properties settings = settingsAsProperties(root.evalNode("settings"));
 5        loadCustomVfs(settings);
 6         loadCustomLogImpl(settings);
 7         typeAliasesElement(root.evalNode("typeAliases"));
 8         pluginElement(root.evalNode("plugins"));
 9         objectFactoryElement(root.evalNode("objectFactory"));
10        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
11        reflectorFactoryElement(root.evalNode("reflectorFactory"));
12     //analysis settings Content in
13        settingsElement(settings);     
14        environmentsElement(root.evalNode("environments"));
15        databaseIdProviderElement(root.evalNode("databaseIdProvider"));
16        typeHandlerElement(root.evalNode("typeHandlers"));
17        mapperElement(root.evalNode("mappers"));
18      } catch (Exception e) {
19        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
20      }
21    }

 

You can probably infer from the method name that you are dealing with those configurations; just look at the 13 lines of code.

settingsElement() source code:

 

 1 private void settingsElement(Properties props) {
 2     configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
 3     configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
 4     configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
 5     configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
 6     configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
 7     configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
 8     configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
 9     configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
10     configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
11     configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
12     configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
13     configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
14     configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
15     configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
16     configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
17     configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
18     configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
19     configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
20     configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
21     configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
22     configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
23     configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
24     configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
25     configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
26     configuration.setLogPrefix(props.getProperty("logPrefix"));
27     configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
28   }

 

Take a look at line 11, which specifies the defaultExecutorType value for configuration.

getProperty (key,defaultValue) has two parameters. The first parameter is the key value of the property. Get the property value according to the key value. If not, use the second parameter as the default value.
If the < setting name = "defaultexecutortype" value = "SIMPLE" / > is not configured, use the SIMPLE value.
Let's talk about the parameters of defaultExecutorType
SIMPLE: ordinary actuator
Re use: the actuator will REUSE (read: worm) the prepared statements (re+use = repeat + use)
The BATCH executor will reuse the statement and perform a BATCH update.

At the beginning, I saw that the official content did not know the meaning of the three, nor did I know whether to read chong or zhong
First change the parameters and run Demo to see the result.
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultExecutorType" value="REUSE"/>
After the two configurations are configured separately, a piece of data can be inserted into the database normally, and the number of rows affected is 1, only
<setting name="defaultExecutorType" value="BATCH"/>
The data returned by this configuration is the following strange thing; neither insert data into the database nor throw exceptions.
Rows affected - 2147482646
1. Both simple and REUSE configurations can operate normally. What's the difference?
2. What is the strange data returned by the batch configuration and why it is not successful.

I haven't said for a long time what Executor is.
The Executor represents the executor. It is used to schedule the StatementdHandler,ParameterHandler,ResultHandler, etc. to execute the corresponding SQL
People familiar with JDBC should be familiar with the above Handler names. If you forget JDBC, take a look at this blog: Don't know jdbc, what about Mybatis source code parsing?
Since the Executor is the general scheduler, it should be called at the place of Mapper's JDK dynamic agent in MyBatis.
If you don't know the dynamic agent, you can take a look at this blog: Looking at JDK dynamic agent from mybatis source code
You can trace it down from the fourth line of code in Demo, getMapper().

 

   

  

The call sequence of getMapper() is as follows:

defaultSqlSession.getMapper()==>configuration.getMapper()==>MapperRegistry.getMapper()==>MapperProxyFactory.newInstance();

According to lines 46 and 47 of the last figure and the call rules of JDK dynamic proxy, it can be inferred that MapperProxy must implement the InvocationHandler interface and the invoke method.

 1 @Override
 2   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 3     try {
 4      //call object Method in
 5       if (Object.class.equals(method.getDeclaringClass())) {
 6         return method.invoke(this, args);
 7       } else if (method.isDefault()) {
 8    //Call the default method in the interface( jdk9 After that, the interface can have a method body)
 9         if (privateLookupInMethod == null) {
10           return invokeDefaultMethodJava8(proxy, method, args);
11         } else {
12           return invokeDefaultMethodJava9(proxy, method, args);
13         }
14       }
15     } catch (Throwable t) {
16       throw ExceptionUtil.unwrapThrowable(t);
17     }
18     final MapperMethod mapperMethod = cachedMapperMethod(method);//Wrap method as MapperMethod object
19     return mapperMethod.execute(sqlSession, args);
20   }

execute() source code:

 

 1 public Object execute(SqlSession sqlSession, Object[] args) {
 2     Object result;
 3     switch (command.getType()) {
 4       case INSERT: {//sql yes insert Sentence
 5         Object param = method.convertArgsToSqlCommandParam(args);
 6         result = rowCountResult(sqlSession.insert(command.getName(), param));
 7         break;
 8       }
 9       case UPDATE: {//sql yes update Sentence
10         Object param = method.convertArgsToSqlCommandParam(args);
11         result = rowCountResult(sqlSession.update(command.getName(), param));
12         break;
13       }
14       case DELETE: {//sql yes delete Sentence
15         Object param = method.convertArgsToSqlCommandParam(args);
16         result = rowCountResult(sqlSession.delete(command.getName(), param));
17         break;
18       }
19       case SELECT://sql yes select Sentence
20         if (method.returnsVoid() && method.hasResultHandler()) {
21           executeWithResultHandler(sqlSession, args);
22           result = null;
23         } else if (method.returnsMany()) {
24           result = executeForMany(sqlSession, args);
25         } else if (method.returnsMap()) {
26           result = executeForMap(sqlSession, args);
27         } else if (method.returnsCursor()) {
28           result = executeForCursor(sqlSession, args);
29         } else {
30           Object param = method.convertArgsToSqlCommandParam(args);
31           result = sqlSession.selectOne(command.getName(), param);
32           if (method.returnsOptional()
33               && (result == null || !method.getReturnType().equals(result.getClass()))) {
34             result = Optional.ofNullable(result);
35           }
36         }
37         break;
38       case FLUSH:
39         result = sqlSession.flushStatements();
40         break; 
41     return result;
42   }

 

The code with comments should know the type of this sql. Our Demo executes the insert statement; so let's look at sqlsession.insert() on line 6

 1 @Override
 2   public int insert(String statement, Object parameter) {
 3     return update(statement, parameter);
 4   }
 5 
 6   @Override
 7   public int update(String statement, Object parameter) {
 8     try {
 9       dirty = true;
10       MappedStatement ms = configuration.getMappedStatement(statement);
   //Remember this place, and we will return to it later. Now we need to find out where the executor comes from and what kind of executor it is.
11 return executor.update(ms, wrapCollection(parameter)); 12 } catch (Exception e) { 13 throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e); 14 } finally { 15 ErrorContext.instance().reset(); 16 } 17 }

The insert method above calls the update method.

In the update method, I finally see the executor variable, and now I finally see the main character of this article.

From the current analysis, we have not seen where the executor is transmitted.

executor is a property of defaultSqlsession,

Since it is both private and final, there is no way to assign a set value, so it must be assigned only when instantiating. Line 57 of the source code in the figure above confirms our view.

Now it's time to find out where the DefaultSqlSession is called for instantiation, and know how the executor is passed.

Take a look at the idea. sqlSession is a database conversation, which is equivalent to the connection in Jdbc.

At this time, we turn to the code in Demo and see the third line of code: SqlSession sqlSession1= sqlSessionFactory1.openSession(true); method.

You can literally understand the sqlSessionFactory factory to create a session object. Continue to track the openSession source code.

 @Override
  public SqlSession openSession(boolean autoCommit) {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
  }

openSessionFromDataSource() passes the executor when the configuration object is built; getDefaultExecutorType() gets the executor of the configuration.

Continue to trace openSessionFromDataSource() source code; remove redundant code

 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //Get actuator because configuration Enumerations are stored, which need to be converted into actual actuator objects according to enumerations
      final Executor executor = configuration.newExecutor(tx, execType);
//instantiation defaultSqlSession;The executor of this database session is determined.
      return new DefaultSqlSession(configuration, executor, autoCommit);   
  }

After determining the executor, continue to return to the above executor.update(ms, wrapCollection(parameter)); method call place.

executor is an interface variable, which must point to an implementation class.

 

Find out the source of executor. Now the update in this place should know which method is bought in the actuator.

According to the configuration, only three classes, simpleexecution, reuseexecution and batchexecutor, can be executed.

Now we can analyze these three classes separately.

SimpleExecutor.java

update() was not found in this. It may be the reason for inheritance. Find it in BaseExecutor.

 

Called the doUpdate() method

 

It's an abstract method, click the inherit button on the left.

 

Continue back to the doUpdate() method in SimpleExecutor.

 1   @Override
 2   public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
 3     Statement stmt = null;
 4     try {
 5       Configuration configuration = ms.getConfiguration();
 6 //StatementHandler Let's talk later
 7       StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
 8 //Obtain Statement Specific object
 9       stmt = prepareStatement(handler, ms.getStatementLog());
10       return handler.update(stmt);
11     } finally {
12       closeStatement(stmt);
13     }
14   }

Continue to trace the prepareStatement() source code

1  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
2     Statement stmt;
3     Connection connection = getConnection(statementLog);
4     //Yes sql Precompile and return precompiled objects.
5     stmt = handler.prepare(connection, transaction.getTimeout());
6    //Yes sql Chinese? Parameter setting
7     handler.parameterize(stmt);
8     return stmt;
9   }

There is a long call chain in the execution of getconnection(), prepare(), parameterize(), and then there is the problem of how to convert the parameters. The involved TypeHandler module is involved. A separate analysis will be opened later.

At this point, the analysis of simpleexecutior executor is completed

Continue analyzing reuseexector.java

Find the prepareStatement() method in the same way as simpleexecutior

 1  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
 2     Statement stmt;
 3     BoundSql boundSql = handler.getBoundSql();
 4   //Obtain sql Sentence
 5     String sql = boundSql.getSql();
 6 //Inquire about this article. sql Whether it has been precompiled or not. If so, get the precompiled object directly
 7     if (hasStatementFor(sql)) {
 8       stmt = getStatement(sql);
 9       applyTransactionTimeout(stmt);
10     } else {
11 //For the first precompile, create a precompiled object directly
12       Connection connection = getConnection(statementLog);
13       stmt = handler.prepare(connection, transaction.getTimeout());
14    //Cache precompiled objects
15       putStatement(sql, stmt);
16     }
17   //sql Parameter substitution in
18     handler.parameterize(stmt);
19     return stmt;
20   }

ReuseExecutor's prepareStatement method has one more sql precompiled object save than SimExecutor's prepareStatement() method.

At this point, I finally understand why the reuse configuration is called reuse (using) sql statement; re (repetition) + use = reuse.

BatchExecutor analysis.

//The code is very big, but just look at the last two lines of code.
 @Override
  public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
    final Configuration configuration = ms.getConfiguration();
    final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
    final BoundSql boundSql = handler.getBoundSql();
    final String sql = boundSql.getSql();
    final Statement stmt;
    if (sql.equals(currentSql) && ms.equals(currentStatement)) {
      int last = statementList.size() - 1;
      stmt = statementList.get(last);
      applyTransactionTimeout(stmt);
      handler.parameterize(stmt);//fix Issues 322
      BatchResult batchResult = batchResultList.get(last);
      batchResult.addParameterObject(parameterObject);
    } else {
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection, transaction.getTimeout());
      handler.parameterize(stmt);    //fix Issues 322
      currentSql = sql;
      currentStatement = ms;
      statementList.add(stmt);
      batchResultList.add(new BatchResult(ms, sql, parameterObject));
    }
    handler.batch(stmt);
    return BATCH_UPDATE_RETURN_VALUE;
  }

First of all, the method itself is very strange; no matter what the execution result is, it directly returns "batch" update "return" value;

Let's see what batch, update, return and value are.

Direct is the minimum value of int plus 1002; I don't know why I want to return such a specific value; why not directly return 1, 2, Zhang San, a Mao and a dog?

Now I finally know why the above insert returns to the following screenshot in the case of BATCH configuration

handler.batch(stmt) source

Directly addBatch() is over. No code is sent to the database to perform the operation, such as ps.executeBatch() method.

Now I finally know why there is no new data in the database.

But why is it so designed? Is the code not finished?

1. Simpleexecutior is the encapsulation of the preparation in jdbc

2. Reuseexecutior caches the preparent encapsulation in Jdbc to achieve the reuse of sql live precompiled objects

3. Bloggers who want to know the answer tell us why the Batch actuator does not write the code to operate the database. If not, what's the use of this actuator?

Tags: Java SQL Mybatis JDBC Database

Posted on Mon, 13 Apr 2020 02:53:42 -0700 by Goldeneye