mybatis创建mapper接口的代理对象来执行sql,并且将查询结果与设置的返回值类型进行匹配。
首先来看一下我们的测试类:
@Test
public void test3() throws IOException {
//获取代理对象
StudentMapper mapper = getSqlSession().getMapper(StudentMapper.class);
//执行目标方法,并映射查询结果
List<Student> students = mapper.selectStudents();
students.forEach(student -> System.out.println(student));
}
private SqlSession getSqlSession() throws IOException {
String resource = "configuration.xml";
InputStream stream = Resources.getResourceAsStream(resource);
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(stream);
SqlSession sqlSession = build.openSession();
return sqlSession;
}
当我们获取到sqlSession对象后,就可以开始生成目标代理对象,并执行目标方法。这里我们就来看看Mybatis是如何获取目标对象并执行目标方法后进行结果映射的:
getMapper:
Mybatis默认的SqlSession实例是DefaultSqlSession的实例:class DefaultSqlSession implements SqlSession
public <T> T getMapper(Class<T> type) {
return this.configuration.getMapper(type, this);
}
调用Configuration下的getMapper方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
它又调用MapperRegistry的getMapper方法:这里才开始创建代理对象
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap(); //map集合
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//创建代理对象工厂
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
//创建代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
创建代理对象之前会先创建代理对象工厂,然后再由代理对象工厂是存储在knownMappers中,它是一个HashMap集合。我们现在所了解的代理对象的实现一般有两种,一种是基于JDK的Proxy,另一种是基于CGLIB的Proxy,而Mybatis使用的事基于JDK创建动态代理对象。我们继续跟进newInstance方法就知道了:
newInstance():
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
这是MapperProxyFactory中的newInstance方法,而它调用的newInstance方法,最终是package java.lang.reflect 包下的Proxy类中的newInstance方法。也就说明它的实现是JDK中的动态代理。
下面我们主要看一下代理对象执行目标方法并将查询结果映射的方法。
代理对象执行目标方法:
mapper.selectStudents():执行目标方法时,首先会执行MapperProxy中的invoke方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
if (this.isDefaultMethod(method)) {
return this.invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
//获取目标方法的实现,因为我们在mapper接口中编写的都是抽象方法,mybatis内部为我们提供了方法实现
MapperMethod mapperMethod = this.cachedMapperMethod(method);
//执行目标方法
return mapperMethod.execute(this.sqlSession, args);
}
我们使用Debug模式进入,看看生成的mapperMethod都有什么:
mapperMethod中存储了方法名,方法执行类型select,还有方法返回值类型、参数列表等信息。
我们重点看一下执行目标方法的过程: mapperMethod.execute(this.sqlSession, args)
MapperMethod的execute方法:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;
switch(this.command.getType()) {
case INSERT:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) { //查询list结果集
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) { //查询单个对象
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) { //游标查询
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}
我们这里执行的事查询操作,且查询的是对象集合,所以这里我们实际执行的是:this.executeForMany()
executeForMany:
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
Object param = this.method.convertArgsToSqlCommandParam(args); //参数转化
List result;
if (this.method.hasRowBounds()) { //根据列序号查询
RowBounds rowBounds = this.method.extractRowBounds(args);
result = sqlSession.selectList(this.command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(this.command.getName(), param);
}
if (!this.method.getReturnType().isAssignableFrom(result.getClass())) {
return this.method.getReturnType().isArray() ? this.convertToArray(result) : this.convertToDeclaredCollection(sqlSession.getConfiguration(), result);
} else {
return result;
}
}
然后开始执行DefaultSqlSession中的selectList方法:
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var5;
try {
MappedStatement ms = this.configuration.getMappedStatement(statement);
var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception var9) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9);
} finally {
ErrorContext.instance().reset();
}
return var5;
}
这里开始执行查询操作,但是这里的查询操作。它先会去查询缓存,如果缓存中没有才回去查询数据库。我们可以看一下从这一步开始执行的方法栈:
熟悉JDBC操作数据库的朋友在这里肯定能找到一些熟悉的类名。
我们这里就不再纠结它查询数据库的过程了,重点关注一下它是如何将查询结果映射到我们设定的结果集中的。
结果集的映射逻辑主要是在DefaultResultSetHandler中的 getRowValue方法:
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException {
String resultMapId = resultMap.getId();
Object rowValue = partialObject;
if (partialObject != null) {
MetaObject metaObject = this.configuration.newMetaObject(partialObject);
this.putAncestor(partialObject, resultMapId);
this.applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
this.ancestorObjects.remove(resultMapId);
} else {
ResultLoaderMap lazyLoader = new ResultLoaderMap();
//创建需要映射的目标对象
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, true)) {
foundValues = this.applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
//将查询的结果与目标对象中的属性映射,如果有一个映射成功即返回true
foundValues = this.applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
this.putAncestor(rowValue, resultMapId);
foundValues = this.applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;
this.ancestorObjects.remove(resultMapId);
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = !foundValues && !this.configuration.isReturnInstanceForEmptyRow() ? null : rowValue;
}
if (combinedKey != CacheKey.NULL_CACHE_KEY) {
this.nestedResultObjects.put(combinedKey, rowValue);
}
}
return rowValue;
}
我们使用debug模式观察rowValue = this.createResultObject(rsw, resultMap, lazyLoader, columnPrefix);执行前后的变化:
执行前:
执行后:
到这里,要映射的目标对象已经被创建完成,下面就开始将查询结果与他进行映射:this.applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix);同样是debug模式查看执行结束后的情况:
student中的基本数据类型都映射完成了,但是grade是对象类型数据,且它是存在的。我们需要先将grade也映射出来,然后再复制给student中的grade属性。applyNestedResultMappings方法中调用了一个方法:this.anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw),它是用来检查对象中有哪些属性可以被赋值但还没有被赋值的,比如grade这种对象类型的数据,并回调getRowValue方法,创建对象并给属性赋值:
private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject) {
boolean foundValues = false;
Iterator var8 = resultMap.getPropertyResultMappings().iterator();
while(true) {
ResultMapping resultMapping;
String nestedResultMapId;
do {
do {
if (!var8.hasNext()) {
return foundValues;
}
resultMapping = (ResultMapping)var8.next();
nestedResultMapId = resultMapping.getNestedResultMapId();
} while(nestedResultMapId == null);
} while(resultMapping.getResultSet() != null);
try {
String columnPrefix = this.getColumnPrefix(parentPrefix, resultMapping);
ResultMap nestedResultMap = this.getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix);
if (resultMapping.getColumnPrefix() == null) {
Object ancestorObject = this.ancestorObjects.get(nestedResultMapId);
if (ancestorObject != null) {
if (newObject) {
this.linkObjects(metaObject, resultMapping, ancestorObject);
}
continue;
}
}
CacheKey rowKey = this.createRowKey(nestedResultMap, rsw, columnPrefix);
CacheKey combinedKey = this.combineKeys(rowKey, parentRowKey);
Object rowValue = this.nestedResultObjects.get(combinedKey);
boolean knownValue = rowValue != null;
this.instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
if (this.anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {
//回调getRowValue方法,创建对象类型数据
rowValue = this.getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);
if (rowValue != null && !knownValue) {//将对象类型变量赋值
this.linkObjects(metaObject, resultMapping, rowValue);
foundValues = true;
}
}
} catch (SQLException var17) {
throw new ExecutorException("Error getting nested result map values for '" + resultMapping.getProperty() + "'. Cause: " + var17, var17);
}
}
}
再将grade传递给student:
至此一条完成的数据查询并映射完成,对于多条数据,依次循环映射,就得到最终的结果。