MyBatis 源碼解析:經過源碼深刻理解 SQL 的執行過程

1、目錄java

一、前言node

二、配置文件加載spring

三、配置文件解析sql

四、SQL執行數據庫

五、結果集映射設計模式

六、Mybatis中的設計模式緩存

七、總結bash

2、前言微信

一、mybatis框架圖mybatis

enter image description here

如上爲mybatis的框架圖,在這篇文章中經過源碼來重點看下數據處理層中的參數映射,SQL解析,SQL執行,結果映射

二、配置使用

獲取mapper並操做數據庫代碼以下:

InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");	
 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
 SqlSession sqlSession = sqlSessionFactory.openSession();
 LiveCourseMapper mapper = sqlSession.getMapper(LiveCourseMapper.class);	
 List<LiveCourse> liveCourseList = mapper.getLiveCourseList();複製代碼

3、配置文件加載

配置文件加載最終仍是經過ClassLoader.getResourceAsStream來加載文件,關鍵代碼以下:

public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
if (in == null) {
  throw new IOException("Could not find resource " + resource);
}
return in;複製代碼

}

InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
for (ClassLoader cl : classLoader) {
  if (null != cl) {

    // try to find the resource as passed
    InputStream returnValue = cl.getResourceAsStream(resource);

    // now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource
    if (null == returnValue) {
      returnValue = cl.getResourceAsStream("/" + resource);
    }

    if (null != returnValue) {
      return returnValue;
    }
  }
}
return null;複製代碼

}

4、配置文件解析

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 咱們以 SqlSessionFactoryBuilder爲入口,看下mybatis是如何解析配置文件,並建立SqlSessionFactory的。SqlSessionFactoryBuilder.build方法實現以下:

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
 //解析出configuration對象,並建立SqlSessionFactory
 return build(parser.parse());複製代碼

重點爲解析configuration對象,而後根據configuration建立DefualtSqlSessionFactory。

一、解析configuration

private void parseConfiguration(XNode root) {
try {
  Properties settings = settingsAsPropertiess(root.evalNode("settings"));
  //issue #117 read properties first
  propertiesElement(root.evalNode("properties"));
  loadCustomVfs(settings);
  typeAliasesElement(root.evalNode("typeAliases"));
  pluginElement(root.evalNode("plugins"));
  objectFactoryElement(root.evalNode("objectFactory"));
  objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
  reflectionFactoryElement(root.evalNode("reflectionFactory"));
  settingsElement(settings);
  // read it after objectFactory and objectWrapperFactory issue #631
  environmentsElement(root.evalNode("environments"));
  databaseIdProviderElement(root.evalNode("databaseIdProvider"));
  typeHandlerElement(root.evalNode("typeHandlers"));
  mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
  throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}複製代碼

}

經過XPathParser解析configuration節點下的properties,settings,typeAliases,plugins,objectFactory,objectWrapperFactory,reflectionFactory,environments,databaseIdProvider,typeHandlers,mappers這些節點。解析過程大致相同,都是經過XPathParser解析相關屬性、子節點,而後建立相關對象,並保存到configuration對象中。這塊代碼相對簡單,你們閱讀起來沒什麼難度。

(1)解析properties 解析properties,並設置到configuration對象下的variables屬性,protected Properties variables = new Properties();

(2)解析settings 解析settings配置,如lazyLoadingEnabled(默認false),defaultExecutorType(默認SIMPLE),jdbcTypeForNull(默認OTHER),callSettersOnNulls(默認false)

(3)解析typeAliases 經過typeAliasRegistry來註冊別名,別名經過key,value的方式來進行存儲,mybatis默認會建立一些基礎類型的別名,如string->String.class,int->Integer.class,map->Map.class,hashmap->HashMap.class,list->List.class。別名和class關係經過HashMap來存儲,

private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();複製代碼

(4)解析plugins 解析插件,而後設置Configuration的InterceptorChain。 Configuration: protected final InterceptorChain interceptorChain = new InterceptorChain();

InterceptorChain:

private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
}複製代碼

在建立的時候構造了攔截器鏈,在執行的時候也會通過攔截器鏈,此處爲典型的責任鏈模式

(5)解析objectFactory 能夠自定義ObjectFactory,對象工廠,默認爲DefaultObjectFactory

(6)解析objectWrapperFactory 默認爲DefaultObjectWrapperFactory

(7)reflectionFactory 反射工廠,在經過反射建立對象時(如結果集對象),能夠經過自定義的反射工廠來建立對象。objectFactory,objectWrapperFactory,reflectionFactory這又是典型的工廠模式,將對象的建立交由相應的工廠來建立。

(8)databaseIdProvider 用來支持不一樣的數據庫,不多在項目中用到

(9)解析typeHandlers 解析TypeHandler並經過typeHandlerRegistry註冊到configuration中,經過TYPE_HANDLER_MAP保存typeHandler:

private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new HashMap<Type, Map<JdbcType, TypeHandler<?>>>();複製代碼

(10)解析mappers 讀取經過url指定的配置文件,而後經過XmlMapperBuilder進行解析

二、解析mapper

解析mapper的入口爲XmlMapperBuilder.parse方法,在解析的時候會解析cache-ref,cache,parameterMap,resultMap,sql,select|insert|update|delete。cache-ref,cache和緩存相關,parameterMap目前已不多使用,這裏就再也不說明了。

2.一、解析resultMap

入口方法爲XmlMapperBuilder.resultMapElement,解析resultMap主要包含以下步驟:

(1)解析resultMap屬性

解析id,type,autoMapping屬性,type取值的優先級爲 type -> ofType -> resultType -> javaType

String type = resultMapNode.getStringAttribute("type",
    resultMapNode.getStringAttribute("ofType",
        resultMapNode.getStringAttribute("resultType",
            resultMapNode.getStringAttribute("javaType"))));複製代碼

(2)解析resultMap下的result子節點,建立ResultMapping對象

resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));複製代碼

解析result節點的property,column,javaType,jdbcType,select,resultMap,notNullColumn,typeHandler,resultSet,foreignColumn,lazy屬性。

此處須要注意的點爲:解析select屬性與resultMap屬性,由於這塊涉及嵌套查詢與嵌套映射(後面在結果集映射時會講下這塊)。若是result節點中存在select屬性則認爲是嵌套查詢,而嵌套映射的判斷條件以下:

String nestedResultMap = context.getStringAttribute("resultMap",
    processNestedResultMappings(context, Collections.<ResultMapping> emptyList()));複製代碼

若是result節點存在resultMap則確定是嵌套映射

private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings) throws Exception {
if ("association".equals(context.getName())
    || "collection".equals(context.getName())
    || "case".equals(context.getName())) {
  if (context.getStringAttribute("select") == null) {
    ResultMap resultMap = resultMapElement(context, resultMappings);
    return resultMap.getId();
  }
}
return null;複製代碼

}

若是是association,collection,case這些節點,而且select屬性爲空的話,則認爲是嵌套映射

(3)註冊ResultMap 經過resultMapResolver.resolve()來解析resultMap屬性,而後建立ResultMap對象,並保存到resultMaps 屬性中。

protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");複製代碼

2.二、解析sql

sql解析相對簡單,主要是解析sql節點,而後保存到sqlFragments

2.三、解析select|insert|update|delete

入口方法爲XMLStatementBuilder.parseStatementNode,解析statementNode主要包含以下步驟:

(1)解析statementNode屬性 屬性主要包括id,parameterMap,parameterType,resultMap,resultType,statementType(默認爲PREPARED,預處理的statement)

(2)解析include 將include替換爲sql片斷,而後移除include節點

(3)解析selectKey Parse selectKey after includes and remove them.

(4)建立sqlSource SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); langDriver默認爲XMLLanguageDriver,此處很重要,請容許我多列點代碼

public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();複製代碼

}

XMLScriptBuilder.parseScriptNode:

public SqlSource parseScriptNode() {
    List<SqlNode> contents = parseDynamicTags(context);
    MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
    SqlSource sqlSource = null;
    if (isDynamic) {
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }複製代碼

一、解析動態節點

List<SqlNode> parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<SqlNode>();
    NodeList children = node.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      XNode child = node.newXNode(children.item(i));
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
        String data = child.getStringBody("");
        TextSqlNode textSqlNode = new TextSqlNode(data);
        //若是包含${}的話則認爲是動態節點
        if (textSqlNode.isDynamic()) {
          contents.add(textSqlNode);
          isDynamic = true;
        } else {
          contents.add(new StaticTextSqlNode(data));
        }
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
        String nodeName = child.getNode().getNodeName();
        NodeHandler handler = nodeHandlers(nodeName);
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        handler.handleNode(child, contents);
        isDynamic = true;
      }
    }
    return contents;
  }複製代碼

若是statement節點下存在子節點,如trim,if,where,那麼statement確定是動態節點;若是statement節點下不存在子節點,可是文本中包含${},那麼也任務是動態節點。

二、建立SqlSource

若是包含動態節點建立DynamicSqlSource,不然建立RawSqlSource

(5)建立MappedStatement並註冊

根據解析出的屬性建立MappedStatement對象,而後註冊到configuration對象中

protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");複製代碼

5、SQL執行

在配置文件解析這一節,咱們解析了configuration,mapper等節點,並建立了SqlSessionFactory,下面咱們就來分析下SQL執行的過程。

(1)建立SqlSession

SqlSession sqlSession = sqlSessionFactory.openSession();

final Environment environment = configuration.getEnvironment();
  final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
  tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
  final Executor executor = configuration.newExecutor(tx, execType);
  return new DefaultSqlSession(configuration, executor, autoCommit);複製代碼

由於沒有和spring進行整合,事務爲JdbcTransaction,executor爲默認的SimpleExecutor,autoCommit爲false

(2)建立mapper代理類

咱們順着DefaultSqlSession.getMapper方法來看下mybatis是如何建立mapper代理類的,

public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }複製代碼

能夠看到最終是會經過mapperProxyFactory來建立MapperProxy代理類,實現代碼以下:

public T newInstance(SqlSession sqlSession) {
	final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
	return newInstance(mapperProxy);
}

protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }複製代碼

經過jdk動態代理來建立最終的Proxy代理類,最終類結構以下圖所示:

enter image description here

MapperProxy實現InvocationHandler接口,在執行mapper方法的時候實際執行的是MapperProxy的invoke方法(對動態代理有疑問的同窗能夠自行補習下)。

(3)調用mapper方法

MapperProxy.invoke方法實現以下:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }複製代碼

若是執行的是Object類的方法,那麼直接執行方法便可;其它方法的話經過MapperMethod來執行。實現以下:

  1. 若是是insert命令,則調用sqlSession.insert方法;
  2. 若是是update命令,則調用sqlSession.update方法;
  3. 若是是delete命令,則調用sqlSession.delete方法;
  4. 若是是select命令,相對insert,update,delete命令來講稍微複雜些,要區分方法的返回值,若是返回List集合的話則調用executeForMany,若是返回單個對象的話則調用selectOne,返回map的話則調用executeForMap

insert,update,delete,select命令它們實現原理都差很少,select只是比它們多告終果集映射這一步,咱們就以select命令的executeForMany方法爲例來講明sql的執行過程。

MapperMethod.executeMany會調用DefaultSqlSession.selectList,而selectList方法實現以下:

//獲取MappedStatement,在mapper解析的時候註冊到configuration對象中的
MappedStatement ms = configuration.getMappedStatement(statement);
//默認爲SimpleExecutor,sql的執行類
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);複製代碼

Executor.query:

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//獲取BoundSql,在此到處理ifwhere,choose動態節點,很重要
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);複製代碼

}

4.一、getBoundSql

public class BoundSql {
  private String sql;
  private List<ParameterMapping> parameterMappings;
  private Object parameterObject;
  private Map<String, Object> additionalParameters;
  private MetaObject metaParameters;複製代碼

BoundSql爲最終執行的sql,爲處理完動態節點後的sql。經過SqlSource來獲取BoundSql,經過前面咱們瞭解到存在兩種SqlSource:DynamicSqlSource,RawSqlSource

4.1.一、DynamicSqlSource.getBoundSql

public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
}複製代碼

在getBoundSql時主要包含以下幾個步驟:

(1)SqlNode.apply

public boolean apply(DynamicContext context) {
    for (SqlNode sqlNode : contents) {
      sqlNode.apply(context);
    }
    return true;
  }複製代碼

在此到處理IfSqlNode,MixedSqlNode,ForEachSqlNode,TrimSqlNode這些動態節點

(2)sqlSourceParser.parse

public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
//將#{}替換爲?,解析出ParameterMappings
String sql = parser.parse(originalSql);
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());複製代碼

}

解析SqlSource,將#{}替換爲?,解析出ParameterMappings,最終生成靜態的StaticSqlSource

public String handleToken(String content) {
  parameterMappings.add(buildParameterMapping(content));
  return "?";
}複製代碼

ParameterMapping主要包括property名稱,jdbcType,javaType,typeHandler。若是未指定javaType的話默認取得是傳遞的參數對象中屬性的類型。

StaticSqlSource.getBoundSql最終返回結果以下:

public BoundSql getBoundSql(Object parameterObject) {
  return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}複製代碼

4.1.二、RawSqlSource.getBoundSql

RawSqlSource相比DynamicSqlSource就簡單多了,在建立RawSqlSource時直接就將sql解析了,getBoundSql時直接建立BoundSql返回便可:

public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
    this(configuration, getSql(configuration, rootSqlNode), parameterType);
  }

  public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> clazz = parameterType == null ? Object.class : parameterType;
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());
  }

  private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
    DynamicContext context = new DynamicContext(configuration, null);
    rootSqlNode.apply(context);
    return context.getSql();
  }


  public BoundSql getBoundSql(Object parameterObject) {
    return sqlSource.getBoundSql(parameterObject);
  }複製代碼

4.二、query

在上面的小節中生成了最終的sql,下面就能夠執行sql了。咱們以SimpleExecutor爲例來看下sql的執行過程:

Configuration configuration = ms.getConfiguration();
  //建立StatementHandler,默認爲PreparedStatementHandler
  StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
  stmt = prepareStatement(handler, ms.getStatementLog());
  return handler.<E>query(stmt, resultHandler);複製代碼

(1)prepareStatement

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt;
Connection connection = getConnection(statementLog);
//設置fetchSize,timeout
stmt = handler.prepare(connection, transaction.getTimeout());
//statement.setParameter sql實際執行參數設置
handler.parameterize(stmt);
return stmt;複製代碼

}

public void parameterize(Statement statement) throws SQLException {
   parameterHandler.setParameters((PreparedStatement) statement);
}複製代碼

最終經過typeHandler.setParameter(ps, i + 1, value, jdbcType);來設置參數

(2)query

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  ps.execute();  //sql執行
  return resultSetHandler.<E> handleResultSets(ps); //處理結果集
}複製代碼

處理結果集也塊相對也比較重要,咱們單獨來說下。

5、結果集映射

方法入口爲DefaultResultSetHandler.handleResultSets,關鍵代碼以下:

public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    if (resultMap.hasNestedResultMaps()) {
      ensureNoRowBounds();
      checkResultHandler();
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }複製代碼

在處理結果集行值時分爲兩部分,處理簡單resultMap對應的行值和處理嵌套resultMap對應的行值,是否嵌套映射在解析mapper resultMap的時候已經解釋過了,這裏再也不重複。處理簡單resultMap對應的行值稍微簡單些,咱們先從簡單的看起吧

5.一、簡單映射

private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
  throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
//處理分頁,跳過指定的行,若是rs類型不是TYPE_FORWARD_ONLY,直接absolute,不然的話循環rs.next
skipRows(rsw.getResultSet(), rowBounds);
//循環處理結果集,獲取下一行值
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
  ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
  //處理行值,重點分析
  Object rowValue = getRowValue(rsw, discriminatedResultMap);
  //保存對象,經過list保存生成的對象Object
  storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
}複製代碼

}

5.1.一、getRowValue

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
Object resultObject = createResultObject(rsw, resultMap, lazyLoader, null);
if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
  final MetaObject metaObject = configuration.newMetaObject(resultObject);
  boolean foundValues = !resultMap.getConstructorResultMappings().isEmpty();
  if (shouldApplyAutomaticMappings(resultMap, false)) {
    foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
  }
  foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
  foundValues = lazyLoader.size() > 0 || foundValues;
  resultObject = foundValues ? resultObject : null;
  return resultObject;
}
return resultObject;複製代碼

}

獲取行值主要包含以下3個步驟:

(1)createResultObject建立結果集對象 根據resultType,經過ObjectFactory.create來建立對象,其實現原理仍是經過反射來建立對象。在建立對象時若是resultMap未配置constructor,經過默認構造方法來建立對象,不然經過有參的構造方法來建立對象

(2)自動映射屬性 若是ResultMap配置了autoMapping="true",或者AutoMappingBehavior爲PARTIAL會自動映射在resultSet查詢列中存在可是未在resultMap中配置的列。

(3)人工映射屬性 映射在resultMap中配置的列,主要包括兩步:獲取屬性的值和設置屬性的值。

//獲取屬性的值
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
//設置屬性的值,經過反射來設置
metaObject.setValue(property, value);複製代碼

獲取屬性的值:

private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
//獲取嵌套查詢對應的屬性值,最終仍是經過Executor.query來獲取屬性值
if (propertyMapping.getNestedQueryId() != null) {
  return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
} else if (propertyMapping.getResultSet() != null) {
  addPendingChildRelation(rs, metaResultObject, propertyMapping);   // TODO is that OK?
  return DEFERED;
} else {
  final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
  final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
  //經過typeHandler來獲取屬性的值,如StringTypeHandler獲取屬性值:rs.getString(columnName)
  return typeHandler.getResult(rs, column);
}複製代碼

}

5.二、嵌套映射

嵌套resultMap主要用來處理collection,association屬性,而且select屬性爲空,如:

<resultMap id="liveCourseMap" type="com.jd.mybatis.entity.LiveCourse">
		<result column="id" property="id"></result>
		<result column="course_name" property="courseName"></result>
		<!-- 經過嵌套映射來獲取關聯屬性的值 -->
		<collection property="users" ofType="com.jd.mybatis.entity.LiveCourseUser">
			<result column="uid" property="id"></result>
			<result column="user_name" property="userName"></result>
			<result column="id" property="liveCourseId"></result>
		</collection>
</resultMap>複製代碼

處理嵌套映射主要包括以下幾個步驟:

(1)skipRows(rsw.getResultSet(), rowBounds); 同簡單映射

(2)createRowKey,根據resultMap下的列建立rowKey,頗有用。在如上liveCourseMap配置中,mybatis將會根據id列和course_name列的值來建立rowKey,相似於相似於-1421739516:769980325:com.jd.mybatis.mapper.LiveCourseMapper.liveCourseMap:id:121:course_name:j2ee

(3)getRowValue

這塊代碼稍微有點繞,我經過例子來講明吧。

select l.id,course_name,u.id uid,u.user_name from jdams_school_live l left join jdams_school_live_users u on l.id = u.live_id where l.yn =1 and l.id = 121 order by course_start_time複製代碼

個人sql很簡單,查詢出課程和參加課程的用戶,結果集以下: enter image description here

mybatis的處理過程爲:

一、處理第一行的值,建立LiveCourse對象(id=121,courseName=j2ee),同時建立User對象(id=1,userName=張三),並放到List中,而後設置LiveCourse的users屬性

二、處理第二行的值,由於在第一行已經建立了LiveCourse對象,因此這一次不會再建立LiveCourse對象,根據rowKey來判斷創沒建立LiveCourse對象(建立完對象會保存)

三、建立User對象(id=2,userName=李四),而後放到List中

大致過程如上所述,咱們再來看下相應的源碼:

//建立rowKey,根據rowKey判斷對應建立沒建立
final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
//建立的LiveCourse對象會保存到nestedResultObjects
Object partialObject = nestedResultObjects.get(rowKey);

 rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
 //只有是第一次建立LiveCourse時纔會進行保存
 if (partialObject == null) {
     storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
    }複製代碼

getRowValue:

Object resultObject = partialObject;
//若是已經建立LiveCoure對象
if (resultObject != null) {
  final MetaObject metaObject = configuration.newMetaObject(resultObject);
  putAncestor(resultObject, resultMapId, columnPrefix);
  //不用建立LiveCouse對象,直接處理嵌套映射便可
  applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
  ancestorObjects.remove(resultMapId);
} else {
  final ResultLoaderMap lazyLoader = new ResultLoaderMap();
  //建立LiveCoure對象,同簡單映射
  resultObject = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
  if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
    final MetaObject metaObject = configuration.newMetaObject(resultObject);
    boolean foundValues = !resultMap.getConstructorResultMappings().isEmpty();
    //自動映射,同簡單映射
    if (shouldApplyAutomaticMappings(resultMap, true)) {
      foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
    }
    //人工映射,同簡單映射
    foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
    putAncestor(resultObject, resultMapId, columnPrefix);
    //處理嵌套映射
    foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;
    ancestorObjects.remove(resultMapId);
    foundValues = lazyLoader.size() > 0 || foundValues;
    resultObject = foundValues ? resultObject : null;
  }
  if (combinedKey != CacheKey.NULL_CACHE_KEY) {
    nestedResultObjects.put(combinedKey, resultObject);
  }
}複製代碼

在處理嵌套映射屬性時,主要是建立對象,設置屬性值,而後添加到外層對象的colletion屬性中

private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) {
final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
//若是外層對象已經有集合屬性值時,直接將建立的對象添加到集合中
if (collectionProperty != null) {
  final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
  targetMetaObject.add(rowValue);
} else {
  //建立集合,而後設置屬性值
  metaObject.setValue(resultMapping.getProperty(), rowValue);
}複製代碼

}

(4)storeObject

保存建立的對象,同簡單映射

6、Mybatis中的設計模式

(1)工廠模式 SqlSessionFactory

(2)動態代理 MapperProxy

(3)組合模式 SqlNode,MixSqlNode,處理SqlNode

(4)模板方法

(5)建造者模式

(6)責任鏈模式 plugins插件處理

7、總結

mybatis源碼相對來講比較簡單,只要抓住主脈絡(mapper解析,sql執行,結果集映射這幾大塊),而後經過debug一步步跟下來,你也能夠深刻了解和理解mybatis 中sql的執行過程。因爲我的時間、精力、水平有限,不免有一些遺漏和錯誤的地方,還請你們多指教,謝謝。

BLOG地址www.liangsonghua.com

關注微信公衆號:松花皮蛋的黑板報,獲取更多精彩!

公衆號介紹:分享在京東工做的技術感悟,還有JAVA技術和業內最佳實踐,大部分都是務實的、能看懂的、可復現的

相關文章
相關標籤/搜索