preface

MyBatis may be used by many people, but the SQL execution process of MyBatis may not be clear to all people, so since it is in, read through this article you will gain as follows:

  • 1. How are Mapper interfaces and mapping files bound

  • 2. Execution process of SQL statement in MyBatis

  • 3. Customize the parameter setting processor typeHandler in MyBatis

  • 4. Customize the result set processor typeHandler in MyBatis

Benefits: MyBatis study notes and source code analysis

PS: This article is based on MyBatis3.5.5 version of the source code

The profile

In MyBatis, the use of programmatic data query, is mainly the following lines of code:

SqlSession session = sqlSessionFactory.openSession(); UserMapper userMapper = session.getMapper(UserMapper.class); A List < LwUser > user List = userMapper. ListUserByUserName (" lone Wolf 1 ");Copy the code

The first line is to obtain a SqlSession object, which was analyzed in the previous article, the second line is to obtain the UserMapper interface, and the third line is a single line of code that implements the entire query statement flow. Next, we will examine the second and third steps in detail.

GetMapper interface (getMapper)

GetMapper (); getMapper (); getMapper (); getMapper (); getMapper ();

  • 1. After getMapper is called, the Mapper object will be retrieved from the Configuration object, because the Mapper interface will be loaded and parsed into the Configuration object when the project starts

  • 2. Continue to call the getMapper method through the MapperRegistry object property in the Configuration object

  • 3. According to type type, the proxy factory class corresponding to the current type is retrieved from knownMappers in the MapperRegistry object, and then the proxy class corresponding to Mapper is generated using the proxy factory class

  • 4. Finally obtain the MapperProxy object corresponding to our interface

MapperProxy, on the other hand, implements InvocationHandler, using the JDK dynamic proxy.

At this point the process of getting the Mapper is over, so the question is when is the data stored in the HashMap attribute knownMappers in the MapperRegistry object?

When Mapper interfaces and mapping files are associated

Mapper interface and its mapping file are stored when loading the mybatis-config configuration file. Here is the sequence diagram:

  • 1. First we call the build() method of the SqlSessionFactoryBuilder method manually:

  • 2. We then construct an XMLConfigBuilder object and call its parse method:

  • 3. The configuration file is then parsed using its own parseConfiguration, where the top-level nodes of the global configuration file are parsed separately, and the rest of the mappers node is then parsed

  • 4. Continue to call your mapperElement to parse the mappers file. As you can see, there are four ways to parse the mappers node configuration, corresponding to four mapper configurations. The two ways in the red box are directly configured XML mapping files, and the two ways in the blue box are parsed directly configured Mapper interface. It can also be shown from here that, no matter which way is configured, MyBatis will eventually associate XML mapping files with Mapper interface.

  • 5. Let’s first look at options 2 and 3 (direct configuration of XML mapping file parsing), which build an XMLMapperBuilder object and call its Parse method.

Of course, this will still be parsed, and the query will be iterated again to complete it, but it is important to note that cross-referencing will cause parsing failures, so we should avoid cyclic dependencies during development.

  • 6. After parsing the mapping file, call its own method bindMapperForNamespace and start binding Mapper interface and mapping file:

  • 7. Call addMapper of the Configuration object

  • The addMapper method in MapperRegistry is used to formally addMapper to knownMappers, so getMapper can get it directly:

This completes the binding of the Mapper interface to the XML mapping file

  • 9. Notice the code in the red box above, which calls the parse method once again. This parse method parses annotations, such as the following statement:
@Select("select * from lw_user")    List<LwUser> listAllUser();
Copy the code

This method parses @select and other annotations. Notice that the parse method parses the XML mapping file again, because mappers are configured in four different ways, two of which are using the Mapper interface. The addMapper interface is directly called to configure the Mapper interface, and the mapping file is not parsed. Therefore, when entering the annotation parsing method parse, you need to try parsing the XML mapping file again.

After parsing, the methods in the Mapper interface are also parsed and the fully qualified class name of each method is stored as a key into the mappedStatements property in the Configuration.

The value is stored twice, with a fully qualified name as the key and only the method name (id) as the key:

So ultimately mappedStatements will look like this:

In fact, if we use an interface to program a getStatement, it will be based on a fully qualified name, so it doesn’t matter if it has the same name. The reason for doing this is to be compatible with earlier versions of the code, which is to not use an interface. Instead, query directly by method name:

session.selectList("com.lonelyWolf.mybatis.mapper.UserMapper.listAllUser");
Copy the code

ShortName = “shortName”; shortName = “shortName”;

session.selectList("listAllUser");
Copy the code

If shortName is repeated, the following exception will be raised:

StrickMap: StrickMap: StrickMap: StrickMap: StrickMap: StrickMap: StrickMap

SQL execution process analysis

As mentioned above, the obtained Mapper interface is actually wrapped as a proxy object, so we execute the query statement must be the proxy object method executed. Next, we use MapperProxy, the proxy object of Mapper interface, to analyze the query process.

The entire SQL execution process can be divided into two steps:

  • Find SQL

  • 2. Execute SQL statements

Looking for SQL

Let’s first look at the sequence diagram to find the SQL statement:

  • Anyone familiar with the proxy pattern will know that invoking a method of a proxied object is actually executing the invoke method of the proxy object

  • 2, Because we are not calling an Object method, we must go else. CachedInvoker = cachedInvoker = cachedInvoker = cachedInvoker = cachedInvoker = cachedInvoker The default method is not an abstract method, so it also needs special processing (at the beginning, it will be taken from the cache, cache related knowledge is not covered here, we will write a separate article to analyze the cache).

  • 3. Construct a MapperMethod object, which encapsulates the corresponding method information in the Mapper interface and the corresponding SQL statement information:

This will parse the SQL statement to be executed, request parameters, method return values into a MapperMethod object, and then prepare to execute the SQL statement

Execute SQL statement

Let’s first look at the sequence diagram for executing the Sql statement:

  • 1, we continue the above process into the execute method:

  • The executeForMany method is executed based on the statement type and the return value type.

  • Select * from selectList (); select * from selectList (); return to SqlSession ();

  • Execute (); queryFromDatabase (); queryFromDatabase ();

  • 4. Now that we’re here, we’ll finally get down to business. Usually, the do method is used to do things, and many places in Spring are named this way:

Note that our SQL Statement is still placeholder and does not set parameters. Therefore, when the Statement object is created by calling the prepareStatement method above the return line, the parameter will be set and the placeholder will be replaced. How to set parameters we’ll skip over that, and we’ll analyze parameter mapping and result set mapping separately when we finish the process.

  • PreparedStatementHandler (PreparedStatementHandler); PreparedStatementHandler (PreparedStatementHandler); PreparedStatement (PreparedStatementHandler);

Here, the entire SQL statement execution process analysis is over, the midway has a storage, and transformation of some parameters are not deep inside, because the parameter is not the core of the conversion, as long as you know the whole process of the transfer of data, we can also have their own way, save as long as we finally able to parse read it out.

Parameter mapping

Now let’s look at how the parameters are set before the query is executed. Let’s go to the prepareStatement method:

Parameterize is called on StatementHandler to set the parameters. To save space, we will not go into the parameter setting method step by step.

BaseTypeHandler is an abstract class. SetNonNullParameter is not implemented. It is implemented by subclasses, each of which is a type corresponding to the database. Here is a default subclass, StringTypeHandler, with no other logic except to set parameters.

You can see that the setString method of JDBC is called in String, and the setInt method is also called in int. If you’ve read my MyBatis parameter configuration, it should be obvious that these subclasses are the typeHandler provided by default. These default TypeHandlers are registered and bound to Java objects by default:

It is because MyBatis provides the mapping of common data types by default, so we can omit parameter mapping when writing Sql. We can directly use the following way, the system can automatically select the appropriate typeHander for mapping according to the type of our parameters:

select user_id,user_name from lw_user where user_name=#{userName}
Copy the code

The above statement is actually equivalent to the following:

select user_id,user_name from lw_user where user_name=#{userName,jdbcType=VARCHAR}
Copy the code

Or we can specify typeHandler directly:

select user_id,user_name from lw_user where user_name=#{userName,jdbcType=VARCHAR,typeHandler=org.apache.ibatis.type.IntegerTypeHandler}
Copy the code

TypeHandler is configured, so we will use the configured typeHandler instead of reading the default mapping. If the typeHandler type does not match, we will error:

As many of you know from this, if we customize a typeHandler, we can then configure it to be our own custom class. So let’s see how do we customize a typeHandler

Custom typeHandler

The BaseTypeHandler interface has four methods, including result set mapping. To save space, the code is not written:

package com.lonelyWolf.mybatis.typeHandler; import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class MyTypeHandler extends BaseTypeHandler<String> { @Override public void setNonNullParameter(PreparedStatement  preparedStatement, int index, String param, JdbcType JdbcType) throws SQLException {system.out.println (" Custom typeHandler has taken effect "); preparedStatement.setString(index,param); }Copy the code

Then we rewrite the above query statement:

select user_id,user_name from lw_user where user_name=#{userName,jdbcType=VARCHAR,typeHandler=com.lonelyWolf.mybatis.typeHandler.MyTypeHandler}
Copy the code

Then execute, and you can see that the custom typeHandler takes effect:

Result set mapping

Now let’s look at the mapping of the result set, returning to the last method of executing the SQL process above:

resultSetHandler.handleResultSets(ps)
Copy the code

The logic in a result set mapping is relatively complex, because there are so many cases to consider that we won’t go into every detail here and dive right into the code that formally parses the result set. The following five code fragments are a simple but complete parsing flow:

We can see from the above code snippet, actually parse the result set is very complex, as we have an introduction of complex queries, a query can be nested query other constantly, there are some lazy loading, and so on the processing of complex features, so logic branch is there are a lot of, but, no matter how to deal with the core is a set of processes, TypeHandler is eventually called to retrieve the results of the query.

Yes, this is the typeHandler from which we mapped the parameters, because typeHandler contains not only a method for setting parameters, but also a method for getting result sets.

Customize the typeHandler result set

So let’s rewrite the value method (omitting the set parameter method) using the MyTypeHandler example above:

package com.lonelyWolf.mybatis.typeHandler; import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; Public class MyTypeHandler extends BaseTypeHandler<String> {/** ** set the parameter * / @override public void setNonNullParameter (PreparedStatement preparedStatement, int index, String param, JdbcType JdbcType) throws SQLException {system.out.println (" Setting parameters -> Custom typeHandler has taken effect "); preparedStatement.setString(index,param); } /** * Override public String getNullableResult(ResultSet ResultSet, String columnName) throws SQLException {system.out. println(" Obtain result based on columnName -> Custom typeHandler is in effect "); return resultSet.getString(columnName); } @override public String getNullableResult(ResultSet ResultSet, Int columnIndex) throws SQLException {system.out.println (" Obtain result according to columnIndex -> Custom typeHandler is in effect "); return resultSet.getString(columnIndex); } @override public String getNullableResult(CallableStatement CallableStatement, int columnIndex) throws SQLException { return callableStatement.getString(columnIndex); }}Copy the code

Rewrite Mapper mapping file configuration:

<resultMap id="MyUserResultMap" type="lwUser">        
<result column="user_id" property="userId" jdbcType="VARCHAR" 
typeHandler="com.lonelyWolf.mybatis.typeHandler.MyTypeHandler" />        
<result column="user_name" property="userName" jdbcType="VARCHAR" />    
</resultMap><select id="listUserByUserName" parameterType="String" resultMap="MyUserResultMap">        

select user_id,user_name from lw_user where user_name=#{userName,jdbcType=VARCHAR,typeHandler=com.lonelyWolf.mybatis.
typeHandler.MyTypeHandler}    
</select>
Copy the code

The output is as follows:

Because we have only one property configured on our property, we only print it once.

Work flow chart

The above introduces the flow of code, which may be a little dizzy, so let’s draw a flow chart between main objects to show the main working process of MyBatis more clearly:

As can be seen from the above workflow flow chart, there are 4 objects below SqlSession, these 4 objects are also very important, after learning the interceptor is for these 4 objects interception, about the details of these 4 objects, we will be analyzed in the next article.

conclusion

This paper mainly analyzes the SQL execution process of MyBatis. In the process of analyzing the flow, we also demonstrated how to customize typeHandler to implement custom parameter mapping and result set mapping. However, the default mapping provided in MyBatis can satisfy most of the requirements. If we need special handling for some attributes, You can use a custom typeHandle to implement it. If you read this article, you should at least have a clear idea of the following points:

  • 1. How are Mapper interfaces and mapping files bound

  • 2. Execution process of SQL statement in MyBatis

  • 3. Customize the parameter setting processor typeHandler in MyBatis

  • 4. Customize the result set processor typeHandler in MyBatis

Of course, many of the details are not mentioned, and look at the source code we do not need to pursue every line of code can understand, for example, we a slightly more complex business system, even if we are project developers if a module is not responsible for myself, I am afraid it is difficult to figure out the meaning of each line of code. So for MyBatis and other framework source code is the same, should start from the overall situation, master the overall process and design ideas, and then if interested in some implementation details, then in-depth understanding.