This article source code from mybatis-spring-boot-starter 2.1.2 version
The previous chapters have introduced the creation of MapperPoxy, the generation of MapperStatement, Executor and other core components, which are actually paving the way for this article. In this article we will look at how Mybatis executes our defined SQL statement. It’s a little long, so LET me show you a picture.
Sequence diagram
Environment to prepare
<! --mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<! - mysql driver - >
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<! -- Database connection pool -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.26</version>
</dependency>
Copy the code
Write Dao and corresponding Mapper:
public interface RoleMapper {
@Select("select * from role")
@Results({
@Result(property = "roleId",column = "role_id"),
@Result(property = "roleName",column = "role_name")})List<Role> selectALl(a);
}
Copy the code
Write unit tests, then we use the test method on the line of MybatisSql statement execution of the exploration.
@RunWith(SpringRunner.class)
@SpringBootTest
public class MybatisApplicationTests {
@Resource
private RoleMapper roleMapper ;
@Test
public void query2(a){ List<Role> roles = roleMapper.selectALl(); }}Copy the code
MapperProxy
When we give the Mapper class to Spring to manage, we actually store MapperProxy, Mapper’s proxy object, in the Spring container. When a proxy object is used to invoke a method in our own interface, the Invoke () method of the InvocationHandler implementation class is executed. Mybatis invoke() invoke(); Mybatis invoke();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
returncachedInvoker(method).invoke(proxy, method, args, sqlSession); }}catch (Throwable t) {
throwExceptionUtil.unwrapThrowable(t); }}Copy the code
CachedInvoker (method).invoke(Proxy, Method, args, sqlSession) Here we see that the final query result is actually cachedInvoker(method) executing the invoke method. Obviously cachedInvoker(Method) is also a proxy object, so let’s take a look at it.
2.1 invoke
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
// The mapping proxy object holds our query method cache and returns the same method the second time.
return methodCache.computeIfAbsent(method, m -> {
if (m.isDefault()) {
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return newDefaultMethodInvoker(getMethodHandleJava9(method)); }}catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw newRuntimeException(e); }}else {
return new PlainMethodInvoker(newMapperMethod(mapperInterface, method, sqlSession.getConfiguration())); }}); }catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null? re : cause; }}Copy the code
First call methodCache.com puteIfAbsent this method, this is a proxy object mapping method our query cache, we call for the first time is certainly no cache, Will enter the new PlainMethodInvoker (new MapperMethod (mapperInterface, method, sqlSession getConfiguration ())). There’s a new MapperMethod object as a PlainMethodInvoker constructor, which we’ll talk about in a minute, but we’ll keep going. PlainMethodInvoker is an inner class of MapperProxy:
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
super(a);this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
returnmapperMethod.execute(sqlSession, args); }}Copy the code
PlainMethodInvoker defines the member property mapperMethod and overwrites the invoke() method. So we’re going to end up with the execute() method of MapperMethod. Let’s see what a MapperMethod is.
MapperMethod
Let’s start with MapperMethod construction:
public MapperMethod(Class
mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
Copy the code
MapperMethod has two command and method attributes, one of which is related to SQL and the other is related to its own methods.
3.1 SqlCommand
SqlCommand as we mentioned earlier, it will get the MappedStatement in the Configuration based on the Mapper interface name and method name.
public SqlCommand(Configuration configuration, Class
mapperInterface, Method method) {
// Get the method name
final String methodName = method.getName();
finalClass<? > declaringClass = method.getDeclaringClass();// Get the MappedStatement interface name + method name
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
if(method.getAnnotation(Flush.class) ! =null) {
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "."+ methodName); }}else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: "+ name); }}}Copy the code
3.2 MethodSignature
MethodSignature does some processing on method entry and exit parameters, such as setting the token based on the method return type, input parameter names, etc., in method, which is used later in ResultSetHandler.
public MethodSignature(Configuration configuration, Class
mapperInterface, Method method) {
// Parse the method return type
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
// Return value type
if (resolvedReturnType instanceofClass<? >) {this.returnType = (Class<? >) resolvedReturnType; }else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class<? >) ((ParameterizedType) resolvedReturnType).getRawType(); }else {
this.returnType = method.getReturnType();
}
this.returnsVoid = void.class.equals(this.returnType);
// Whether to return multi-tune results
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
this.returnsCursor = Cursor.class.equals(this.returnType);
this.returnsOptional = Optional.class.equals(this.returnType);
this.mapKey = getMapKey(method);
// Whether the return value is MAP
this.returnsMap = this.mapKey ! =null;
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
//resultHandler type parameter location
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
Copy the code
3.3 the execute
// This method is a wrapper call to SqlSession
public Object execute(SqlSession sqlSession, Object[] args) {
// Define the return result
Object result;
// If it is an INSERT operation
if (SqlCommandType.INSERT == command.getType()) {
// Process parameters
Object param = method.convertArgsToSqlCommandParam(args);
// Call the INSERT method of sqlSession
result = rowCountResult(sqlSession.insert(command.getName(), param));
// Same as above for UPDATE
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
// Do the same for DELETE
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
SQL > SELECT (); SQL > SELECT (); SQL > SELECT ()
} else if (SqlCommandType.SELECT == command.getType()) {
// If void is returned and the argument has resultHandler
Void select(String Statement, Object parameter, ResultHandler handler); methods
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
List
selectList(String Statement, Object parameter);
// the executeForMany method is called
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
// Call the executeForMap method if the return type is MAP
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else {
// Query a single objectObject param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); }}else if (SqlCommandType.FLUSH == command.getType()) {
result = sqlSession.flushStatements();
} else {
// If none match, mapper defines the wrong method
throw new BindingException("Unknown execution method for: " + command.getName());
}
// Throw an exception if the return value is null and the method return value type is base and not VOID
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
Depending on the type of SQL statement, it looks like parAM is fetched and then routed to a sqlSession method. Since our selectAll() gets returnsMany=true when executing the MethodSignature method, we follow up
3.4 executeForMany
If the argument contains rowBounds, the paging query is called
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
// Call a normal query if there is no paging
result = sqlSession.<E>selectList(command.getName(), param);
}
Copy the code
The selectList() method of sqlSession continues here:
SqlSession
4.1 SqlSessionTemplate
MapperFactoryBean defines the SqlSession type SqlSessionTemplate in getObject() before creating MapperFactory.
@Override
public T getObject(a) throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
/ / = = >
public SqlSession getSqlSession(a) {
return this.sqlSessionTemplate;
}
Copy the code
Let’s look at the selectList() method of the SqlSessionTemplate
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.sqlSessionProxy.selectList(statement, parameter);
}
Copy the code
SqlSessionProxy is once again a proxy object, continuing with the invoke method:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Get the SqlSqlSession object, if there is no special binding call sqlSessionFactory openSession creation
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
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) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if(translated ! =null) { unwrapped = translated; }}throw unwrapped;
} finally {
if(sqlSession ! =null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); }}}}Copy the code
GetSqlSession if no special configuration is called sessionFactory. OpenSession (executorType) to create:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
/ / create the executor
final Executor executor = configuration.newExecutor(tx, execType);
/ / return DefaultSqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally{ ErrorContext.instance().reset(); }}Copy the code
- The SqlSession implementation class returned is
DefaultSqlSession
. - ExecutorTy defaults to SIMPLE, which is instantiated here
CachingExecutor
Entrusted toSimpleExecutor
Executing specific SQL methods, which have been analyzed in previous articles, is not covered here.
4.2 DefaultSqlSession
Let’s continue with the selectList() method of DefaultSqlSession:
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
/ / get MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
// Call executor's query method
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
5.1 CachingExecutor
As mentioned above, CachingExecutor is instantiated when SqlSession is created. Let’s look at the CachingExecutor query method:
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// Get the cache
Cache cache = ms.getCache();
if(cache ! =null) {
// Whether the cache needs to be cleared
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// The implementation class delegated to BaseExecutor continues execution
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// Put it in the cache
tcm.putObject(cache, key, list); // issue #578 and #116
}
returnlist; }}// The implementation class delegated to BaseExecutor continues execution
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
Copy the code
CachingExecutor checks to see if there is a cache. If there is no cache, the cache will be written to. Delegat is the delegate object for CachingExecutor, which defaults to SimpleExecutor.
5.2 BaseExecutor
Let’s look at the BaseExecutor parent’s query:
@Override
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.");
}
// Whether to clear the local cache
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// If the query statement already exists in the level-1 cache, it is directly fetched from level-1, whereas it is read from the database
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if(list ! =null) {
// Here we try to process expressions of type Callable, mainly for parameters of type mode=out
// Used by stored procedures
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// Fetch it from the database and cache it, which also calls the doQuery() method that subclasses need to overridelist = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); }}finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
// If the level 1 cache scope is statement level, the level 1 cache is cleared with each query
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482clearLocalCache(); }}return list;
}
Copy the code
Here is the use of the MyBatis cache, which we will explain in detail later. If the level 1 cache is at the Statement level, it is cleared after each use. Let’s go straight to the queryFromDatabase() method.
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
// Place placeholders in the cache
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// Delete the placeholder
localCache.removeObject(key);
}
// Add cache
localCache.putObject(key, list);
// The stored procedure out parameter is also added to the cache and can be ignored here
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
Copy the code
DoQuery () is the abstract method, and this is actually the doQuery() method that executes SimpleExecutor.
5.3 SimpleExecutor
@Override
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 StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// Create a Statement object. Note that this is the java.sql.Statement object for JDBC
stmt = prepareStatement(handler, ms.getStatementLog());
// Execute the SQL statement
return handler.query(stmt, resultHandler);
} finally{ closeStatement(stmt); }}Copy the code
There are three main things about doQuery:
- use
configuration.newStatementHandler
To create a StatementHandler, we’re inSection in the previous chapterAs already mentioned, there is no more to explain here. - Create a Statement (java.sql.Statement) object.
- Execute SQL statements using the Statement object.
Creating a Statement object
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
Copy the code
6.1 Obtaining a Java.sql.Connection Object
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
returnconnection; }}Copy the code
This transaction is Srping transaction manager SpringManagedTransaction.
@Override
public Connection getConnection(a) throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}
Copy the code
Continuing with openConnection():
private void openConnection(a) throws SQLException {
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
LOGGER.debug(() -> "JDBC Connection [" + this.connection + "] will"
+ (this.isConnectionTransactional ? "" : " not ") + "be managed by Spring");
}
Copy the code
DataSource is our configuration data source in the yml, call org. Springframework). The JDBC dataSource. The DataSourceUtils doGetConnection ().
6.2 Creating a Java.sql.Statement Object
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: "+ e, e); }}Copy the code
- The Statement object is created from the Connection object, with its default implementation class
PreparedStatementHandler
theinstantiateStatementMethods. - Set the timeout period for the Statement. The timeout period for the Statement is not the timeout period for the entire query, but the timeout period for the Statement to return when the Statement is executed and fetchSize is fetched.
- Set FetchSize for Statement. This parameter is necessary when a large amount of data is queried
fetchSize
Otherwise full pull is easy OOM.
Continue looking at instantiateStatement() :
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
returnconnection.prepareStatement(sql, keyColumnNames); }}else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.prepareStatement(sql);
} else {
returnconnection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY); }}Copy the code
Connection. PrepareStatement is the core of the create Statement object, the connection is a druid connection, by a druid. DruidPooledPreparedStatement rtnVal = new DruidPooledPreparedStatement (this, stmtHolder).
Execute Sql
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// druid's execute is called
ps.execute();
// Execution result
return resultSetHandler.handleResultSets(ps);
}
Copy the code
7.1 the execute
Because our data source connection pool is configured with Druid. Execute calls druid’s implementation:
@Override
public boolean execute(a) throws SQLException {
checkOpen();
// Increase the number of SQL executions
incrementExecuteCount();
// Save the SQL in transactionInfo
transactionRecord(sql);
// oracleSetRowPrefetch();
conn.beforeExecute();
try {
// Execute JDBC
return stmt.execute();
} catch (Throwable t) {
throw checkException(t);
} finally{ conn.afterExecute(); }}Copy the code
7.2 handleResultSets
7.2.1 Entry of Mapping Results
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;
// The first result of the result set
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while(rsw ! =null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
// Generate Java objects according to resultMap processing RSW
handleResultSet(rsw, resultMap, multipleResults, null);
// Get the next result of the result set
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if(resultSets ! =null) {
// Similar to the traversal handling of resultMaps
while(rsw ! =null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if(parentMapping ! =null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
// Process the result
handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; }}return collapseSingleResultList(multipleResults);
}
Copy the code
ResultMap is the core of handleResultSets method, when mybatis initialization after the completion of the above configuration in MappedStatement. ResultMaps inside, During parsing, the corresponding resultMap is obtained through resultmap. id and then parsed one by one. HandleResultSet (RSW, resultMap, NULL, parentMapping)
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if(parentMapping ! =null) {
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
if (resultHandler == null) {
// Create a default result handler
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
// Process the row data of the result set
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
// Add results to multipleResults
multipleResults.add(defaultResultHandler.getResultList());
} else {
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null); }}}finally {
// issue #228 (close resultsets)closeResultSet(rsw.getResultSet()); }}Copy the code
It can be seen that although there are 3 cases according to parentMapping and resultHandler, they all enter handleRowValues method in the end. Let’s look at the handleRowValues method:
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler
resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
// A nested structure
if (resultMap.hasNestedResultMaps()) {
ensureNoRowBounds();
checkResultHandler();
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
// To deal with simple mappings, this article will analyze only simple mappings firsthandleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); }}Copy the code
This article examines simple mappings only, and I’ll write a separate analysis of the handling of nested structures later. Continue to see handleRowValuesForSimpleResultMap:
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler
resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
ResultSet resultSet = rsw.getResultSet();
// Locate the specified row record according to RowBounds
skipRows(resultSet, rowBounds);
while(shouldProcessMoreRows(resultContext, rowBounds) && ! resultSet.isClosed() && resultSet.next()) {// Use discriminate to find suitable ResultMap
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
// Generate Java objects with lazy loading
Object rowValue = getRowValue(rsw, discriminatedResultMap, null); storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); }}Copy the code
7.2.2 Creating Entity Objects
Let’s trace createResultObject in getRowValue() to see how Java objects are created:
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
this.useConstructorMappings = false; // reset previous mapping result
finalList<Class<? >> constructorArgTypes =new ArrayList<>();
final List<Object> constructorArgs = new ArrayList<>();
// Create an object
Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
if(resultObject ! =null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
// issue gcode #109 && issue #149
/* If lazy loading is enabled, proxy classes are generated for resultObject. If only configured associative queries are enabled, proxy classes are not created without lazy loading. * Proxy classes are created using the Javassist framework by default. * Because entity classes typically do not implement interfaces, you cannot use the JDK dynamic proxy API to generate proxies for entity classes. * and pass in the lazyLoader */
if(propertyMapping.getNestedQueryId() ! =null && propertyMapping.isLazy()) {
resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
break; }}}this.useConstructorMappings = resultObject ! =null && !constructorArgTypes.isEmpty(); // set current mapping result
return resultObject;
}
Copy the code
Moving on to Create objects:
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List
> constructorArgTypes, List
>
throws SQLException {
finalClass<? > resultType = resultMap.getType();final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
if (hasTypeHandlerForResultObject(rsw, resultType)) {
// There are typeHanlder classes that are applicable, which in fact are generally primitive data types or their encapsulated classes
return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
} else if(! constructorMappings.isEmpty()) {// Constructor mapping with arguments
return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
} else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
// interface or no-argument constructor
return objectFactory.create(resultType);
} else if (shouldApplyAutomaticMappings(resultMap, false)) {
// Automatic mapping with argument constructors
return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs, columnPrefix);
}
throw new ExecutorException("Do not know how to create an instance of " + resultType);
}
Copy the code
7.2.3 Result Set Mapping
Here we return to getRowValue (), whether to automatically map respectively do the processing, we first see processing automatic mapping applyAutomaticMappings, his judgment on the condition that shouldApplyAutomaticMappings:
private boolean shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested) {
//resultMap configures autoMapping = true
if(resultMap.getAutoMapping() ! =null) {
return resultMap.getAutoMapping();
} else {
// XML setting autoMappingBehavior: NONE, PARTIAL, FULL
if (isNested) {
return AutoMappingBehavior.FULL == configuration.getAutoMappingBehavior();
} else {
returnAutoMappingBehavior.NONE ! = configuration.getAutoMappingBehavior(); }}}Copy the code
applyAutomaticMappings
: There are columns in the result set, but the resultMap is not configured.
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
// Create a mapping pair for automatic mapping
List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
boolean foundValues = false;
if(! autoMapping.isEmpty()) {for (UnMappedColumnAutoMapping mapping : autoMapping) {
// Get the specified column data from the result set via TypeHandler
final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
if(value ! =null) {
foundValues = true;
}
if(value ! =null|| (configuration.isCallSettersOnNulls() && ! mapping.primitive)) {// gcode issue #377, call setter on nulls (value is not 'found')
// Set value to the specified field of the entity-class object via the meta-information objectmetaObject.setValue(mapping.property, value); }}}return foundValues;
}
Copy the code
private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
final String mapKey = resultMap.getId() + ":" + columnPrefix;
//autoMappingsCache is used as a cache and is first fetched from the cache
List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
// Cache failed to hit
if (autoMapping == null) {
autoMapping = new ArrayList<UnMappedColumnAutoMapping>();
final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
for (String columnName : unmappedColumnNames) {
String propertyName = columnName;
if(columnPrefix ! =null && !columnPrefix.isEmpty()) {
// When columnPrefix is specified,
// ignore columns without the prefix.
if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
propertyName = columnName.substring(columnPrefix.length());
} else {
continue; }}/ / hump
final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
// The set method exists
if(property ! =null && metaObject.hasSetter(property)) {
// The resultMap mapping already exists and is ignored
if (resultMap.getMappedProperties().contains(property)) {
continue;
}
finalClass<? > propertyType = metaObject.getSetterType(property);if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
finalTypeHandler<? > typeHandler = rsw.getTypeHandler(propertyType, columnName); autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
} else {
/ / not found, according to the autoMappingUnknownColumnBehavior attributes (defaults to NONE) processing: NONE (ignore), WARNING (log. Warn), ERROR (cast)configuration.getAutoMappingUnknownColumnBehavior() .doAction(mappedStatement, columnName, property, propertyType); }}else {
/ / / / not found, according to the autoMappingUnknownColumnBehavior properties (the default is NONE) processing: NONE (ignore), WARNING (log. Warn), ERROR (cast)configuration.getAutoMappingUnknownColumnBehavior() .doAction(mappedStatement, columnName, (property ! =null)? property : propertyName,null); }}// add cache
autoMappingsCache.put(mapKey, autoMapping);
}
return autoMapping;
}
Copy the code
applyPropertyMappings
: according to the<resultMap>
The mapping is configured on the node
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
if(propertyMapping.getNestedResultMapId() ! =null) {
// the user added a column attribute to a nested result map, ignore it
// Nested query properties, ignoring column
column = null;
}
if(propertyMapping.isCompositeResult() || (column ! =null&& mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) || propertyMapping.getResultSet() ! =null) {
/ / field values
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
// issue #541 make property optional
final String property = propertyMapping.getProperty();
if (property == null) {
continue;
} else if (value == DEFERED) {
foundValues = true;
continue;
}
if(value ! =null) {
foundValues = true;
}
// Assign to the object
if(value ! =null|| (configuration.isCallSettersOnNulls() && ! metaObject.getSetterType(property).isPrimitive())) {// gcode issue #377, call setter on nulls (value is not 'found')metaObject.setValue(property, value); }}}return foundValues;
}
Copy the code
Obtain the ResultMapping collection of mapping objects from ResultMap. Then the ResultMapping collection is traversed, and getPropertyMappingValue is called during this process to obtain the specified column data, and finally the obtained data is set to the entity class object.
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping,ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
if(propertyMapping.getNestedQueryId() ! =null) {
// Obtain the associated query result
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
} else if(propertyMapping.getResultSet() ! =null) {
addPendingChildRelation(rs, metaResultObject, propertyMapping);
return DEFERED;
} else {
finalTypeHandler<? > typeHandler = propertyMapping.getTypeHandler();final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
Retrieves the value of the specified column from the ResultSet
returntypeHandler.getResult(rs, column); }}Copy the code
HandleResultSets can be used to query handleResultSets.