以下面幾行代碼進行深度分析:
String resource = "mybatis-config.xml";
InputStream in = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = sqlSessionFactory.openSession();
EmployeeDao employeeDao = sqlSession.getMapper(EmployeeDao.class);
employeeDao.queryById(3);
// List empList = employeeDao.queryAll();
// for (Object e : empList)
// System.out.println(e);
一、前三行代碼
主要功能:讀取並解析mybatis核心配置文件到內存中。
分析mapper的解析情況,核心配置文件中註冊接口,跟蹤源碼也可看到分爲package標籤批量註冊
和mapper標籤的url或class或resource屬性單個註冊
。
使用package標籤批量註冊的時候,會多一步掃描當前包,最後都是讀入SqlSessionFactory的configuration屬性的
。
HashMap中,key爲接口的類,value則是代理工廠。
MapperAnnotationBuilder的parse( )方法:
XMLStatementBuilder中parseStatementNode( )方法的源碼:
public void parseStatementNode() {
//取到id
String id = this.context.getStringAttribute("id");
String databaseId = this.context.getStringAttribute("databaseId");
if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
String nodeName = this.context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = this.context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = this.context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", false);
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(this.configuration, this.builderAssistant);
includeParser.applyIncludes(this.context.getNode());
String parameterType = this.context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = this.resolveClass(parameterType);
String lang = this.context.getStringAttribute("lang");
LanguageDriver langDriver = this.getLanguageDriver(lang);
this.processSelectKeyNodes(id, parameterTypeClass, langDriver);
String keyStatementId = id + "!selectKey";
keyStatementId = this.builderAssistant.applyCurrentNamespace(keyStatementId, true);
Object keyGenerator;
if (this.configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = this.configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = this.context.getBooleanAttribute("useGeneratedKeys", this.configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
//解析sql語句
SqlSource sqlSource = langDriver.createSqlSource(this.configuration, this.context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(this.context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = this.context.getIntAttribute("fetchSize");
Integer timeout = this.context.getIntAttribute("timeout");
String parameterMap = this.context.getStringAttribute("parameterMap");
String resultType = this.context.getStringAttribute("resultType");
Class<?> resultTypeClass = this.resolveClass(resultType);
String resultMap = this.context.getStringAttribute("resultMap");
String resultSetType = this.context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = this.resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = this.configuration.getDefaultResultSetType();
}
String keyProperty = this.context.getStringAttribute("keyProperty");
String keyColumn = this.context.getStringAttribute("keyColumn");
String resultSets = this.context.getStringAttribute("resultSets");
this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
}
這裏解析sql語句的時候,明顯分了動態sql和非靜態兩種情況解析。
Mybatis讀取參數的#{ }
都出來了,有種守得雲開見月明的感覺了。
GenericTokenParser的parse( … )方法源碼(生成預編譯的sql語句將#{ }轉成?
):
public String parse(String text) {
if (text != null && !text.isEmpty()) {
int start = text.indexOf(this.openToken);
if (start == -1) {
return text;
} else {
char[] src = text.toCharArray();
int offset = 0;
StringBuilder builder = new StringBuilder();
for(StringBuilder expression = null; start > -1; start = text.indexOf(this.openToken, offset)) {
if (start > 0 && src[start - 1] == '\\') {
builder.append(src, offset, start - offset - 1).append(this.openToken);
offset = start + this.openToken.length();
} else {
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + this.openToken.length();
int end;
for(end = text.indexOf(this.closeToken, offset); end > -1; end = text.indexOf(this.closeToken, offset)) {
if (end <= offset || src[end - 1] != '\\') {
expression.append(src, offset, end - offset);
break;
}
expression.append(src, offset, end - offset - 1).append(this.closeToken);
offset = end + this.closeToken.length();
}
if (end == -1) {
builder.append(src, start, src.length - start);
offset = src.length;
} else {
builder.append(this.handler.handleToken(expression.toString()));
offset = end + this.closeToken.length();
}
}
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
} else {
return "";
}
}
二、第四行代碼
新建一個SqlSession感覺就是創建一個非自動提交的事務,事務隔離級別爲空(默認)。
三、第五行代碼
這時候通知JVM要對之前接口中的所有方法都進行代理,利用代理工廠創建代理對象即MapperProxy。
四、第六行及以後代碼
接口方法執行前,會調用MapperProxy的invoke方法,通過該類匿名內部類又交給靜態內部類去代理。
主要還是看一下查詢,Mybatis是如何將查詢到的字段名和實體類的屬性名對應的。
1、XML中自定義了resultMap
測試的具體sql語句如下:
<mapper namespace="com.cj.dao.EmployeeDao">
<resultMap id="empsMap" type="employee">
<id column="emp_id" property="empId"/>
<result column="emp_name" property="empName"/>
<result column="emp_sex" typeHandler="com.cj.handler.MyTypeHandler" property="empSex"/>
<result column="emp_salary" property="empSalary"/>
<result column="emp_manager_id" property="empManagerId"/>
<result column="emp_dept_id" property="empDeptId"/>
</resultMap>
<select id="queryAll" resultMap="empsMap">
select * from emp
</select>
</mapper>
this.method.hasRowBounds()是判斷rowBoundsIndex是否爲空,這個可能和Mybatis分頁插件有關。
BaseExecutor類的doQuery方法是抽象方法,子類中實現。
執行器的prepareStatement( … )方法創建了JDBC的連接。
因爲使用了log4j日誌工具,所以Connection也被代理了。
ResultSetWrapper類對象rsw就是查詢的結果集,放在循環裏面,一次取一條出來。
自定義了resultMap之後,applyAutomaticMappings函數不會執行主邏輯。只有當resultType的時候纔會執行主邏輯。
Object rowValue = this.createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
這行代碼大概就是反射創建對象。
調用configuration的newMetaObject方法,使得MetaObject類的objectWrapper中的object屬性的和傳入參數的object指向同一個堆空間中的對象。
分析自定義resultMap情況下不會執行applyAutomaticMappings函數主邏輯的原因:
mappedColumns的集合需要從resultMap裏面尋找映射的列名,現在是自定義的resultMap,當然可以找到,所以unMappedColumnNamesMap這個哈希表裏面肯定是空的。
再來看applyAutomaticMappings( … )方法,迭代器爲空直接返回。
所以當自定義resultMap的時候,映射邏輯還是在applyPropertyMappings( … )方法中執行。
applyPropertyMappings函數先讀取mappedColumnNamesMap哈希表中的列名。主要就是根據ResultMappings裏面的字段類型與實體類的一對一關係,通過迭代器將查詢出來的結果賦值(經過相應的TypeHandler)。返回類型爲resultType時,迭代器爲空就直接返回。
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
Iterator var9 = propertyMappings.iterator();
while(true) {
while(true) {
Object value;
String property;
do {
ResultMapping propertyMapping;
String column;
do {
if (!var9.hasNext()) {
return foundValues;
}
propertyMapping = (ResultMapping)var9.next();
column = this.prependPrefix(propertyMapping.getColumn(), columnPrefix);
if (propertyMapping.getNestedResultMapId() != null) {
column = null;
}
} while(!propertyMapping.isCompositeResult() && (column == null || !mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) && propertyMapping.getResultSet() == null);
value = this.getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
property = propertyMapping.getProperty();
} while(property == null);
if (value == DEFERRED) {
foundValues = true;
} else {
if (value != null) {
foundValues = true;
}
if (value != null || this.configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive()) {
metaObject.setValue(property, value);
}
}
}
}
}
查詢結果最後還是要經過相應的TypeHandler。
最後返回rowValue。這也可以應證resultMap如果不寫出相應對象的字段映射標籤的話,其值會是空的。
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
ResultLoaderMap lazyLoader = new ResultLoaderMap();
Object rowValue = this.createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !this.hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
MetaObject metaObject = this.configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (this.shouldApplyAutomaticMappings(resultMap, false)) {
foundValues = this.applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
foundValues = this.applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = !foundValues && !this.configuration.isReturnInstanceForEmptyRow() ? null : rowValue;
}
return rowValue;
}
2、XML使用默認的resultType
<mapper namespace="com.cj.dao.EmployeeDao">
<select id="queryById" resultType="employee">
select emp_name from emp where emp_id=#{id}
</select>
</mapper>
具體執行流程和之前自定義resultMap是絕大部分都是相同的。
返回結果集處理那裏,基本也是一樣的。只是此時執行的映射主邏輯是applyAutomaticMappings( … )方法裏面的。
此時resultMap爲空,unMappedColumnNamesMap中有值。而且映射的時候,會經過TypeHandler。
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
List<DefaultResultSetHandler.UnMappedColumnAutoMapping> autoMapping = this.createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
boolean foundValues = false;
if (!autoMapping.isEmpty()) {
Iterator var7 = autoMapping.iterator();
while(true) {
DefaultResultSetHandler.UnMappedColumnAutoMapping mapping;
Object value;
do {
if (!var7.hasNext()) {
return foundValues;
}
mapping = (DefaultResultSetHandler.UnMappedColumnAutoMapping)var7.next();
//經過TypeHandler
value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
if (value != null) {
foundValues = true;
}
} while(value == null && (!this.configuration.isCallSettersOnNulls() || mapping.primitive));
metaObject.setValue(mapping.property, value);
}
} else {
return foundValues;
}
}
再看看createAutomaticMappings( … )方法的源碼,駝峯命名法!
private List<DefaultResultSetHandler.UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
String mapKey = resultMap.getId() + ":" + columnPrefix;
List<DefaultResultSetHandler.UnMappedColumnAutoMapping> autoMapping = (List)this.autoMappingsCache.get(mapKey);
if (autoMapping == null) {
autoMapping = new ArrayList();
List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
Iterator var8 = unmappedColumnNames.iterator();
while(true) {
while(true) {
String columnName;
String propertyName;
while(true) {
if (!var8.hasNext()) {
this.autoMappingsCache.put(mapKey, autoMapping);
return (List)autoMapping;
}
columnName = (String)var8.next();
propertyName = columnName;
if (columnPrefix == null || columnPrefix.isEmpty()) {
break;
}
if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
propertyName = columnName.substring(columnPrefix.length());
break;
}
}
//熟悉的駝峯命名法配置
String property = metaObject.findProperty(propertyName, this.configuration.isMapUnderscoreToCamelCase());
if (property != null && metaObject.hasSetter(property)) {
if (!resultMap.getMappedProperties().contains(property)) {
Class<?> propertyType = metaObject.getSetterType(property);
if (this.typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
((List)autoMapping).add(new DefaultResultSetHandler.UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
} else {
this.configuration.getAutoMappingUnknownColumnBehavior().doAction(this.mappedStatement, columnName, property, propertyType);
}
}
} else {
this.configuration.getAutoMappingUnknownColumnBehavior().doAction(this.mappedStatement, columnName, property != null ? property : propertyName, (Class)null);
}
}
}
} else {
return (List)autoMapping;
}
}
最後返回foundValues。