以下面几行代码进行深度分析:
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。