接着深入源码分析mybatis查询原理(二)继续讨论。
private static void t1() {
// TODO Auto-generated method stub
// 加载配置
try {
Reader reader = Resources.getResourceAsReader("mybatisconfig.xml");
// 创建
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// 解析资源
SqlSessionFactory factory = builder.build(reader);
Configuration configuration = factory.getConfiguration();
// 打开session
SqlSession session = factory.openSession();
// 2,用接口映射的形式进行查询,官方推荐
empmapper empmapper = session.getMapper(empmapper.class);
List<Emp> list = empmapper.queryall();
for (Emp emp : list) {
System.out.println(emp);
}
List<Emp> list2 = empmapper.queryall();
System.out.println(list2);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
还是这段代码继续分析,看到
List<Emp> list = empmapper.queryall();这么一句代码,那么看看mybatis是如何根据接口方法查询数据库,返回结果的呢,我们一步步分析。
断点进入方法,会执行MapperProxy.invoke()方法。
@Override
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);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
调用invoke方法,参数proxy就是Mapper的代理对象,method是Mapper接口的信息,args是调用Mapper方法的参数列表,调用方法empmapper.queryall();,那么args就是null。
执行invoke方法,先判断被代理是不是一个类,是一个类,那么执行该类的方法,显然我们这里不满足,则执行
final MapperMethod mapperMethod = cachedMapperMethod(method);这个方法是获取MapperMethod,那么MapperMethod是怎么获取的呢?MapperMethod又是什么呢?
跟踪代码进入cachedMapperMethod方法,可以看到先是从缓存中获取,如果缓存中没有,则创建MapperMethod,其实是当mapper方法被调用的时候对应的MapperProxy会生成相应的MapperMethod并且会缓存起来,这样当多次调用同一个mapper方法时候只会生成一个MapperMethod,提高了时间和内存效率。每一个MapperMethod对应了一个mapper文件中配置的一个sql语句或FLUSH配置。
来看看MapperMethod的创建
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
可以看出,创建了 SqlCommand 和 MethodSignature,这两个类都是MapperMethod的内部类,这两个类作用什么呢?先看SqlCommand ,源码看SqlCommand 创建时都做了什么。
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
String statementName = mapperInterface.getName() + "." + method.getName();
MappedStatement ms = null;
if (configuration.hasStatement(statementName)) {
ms = configuration.getMappedStatement(statementName);
} else if (!mapperInterface.equals(method.getDeclaringClass())) { // issue #35
String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
if (configuration.hasStatement(parentStatementName)) {
ms = configuration.getMappedStatement(parentStatementName);
}
}
if (ms == null) {
if(method.getAnnotation(Flush.class) != null){
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): " + statementName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
这个mapperInterface是mapper接口的全限定名,那么这里的statementName就是全限定类名+方法名,对应于这个查询就是:
com.shandian.mapper.empmapper.queryall,那么接下来就是通过statementName获取一个MappedStatement,这又是什么东东呢?
那么我们来看看MappedStatement类,
public final class MappedStatement {
private String resource;
private Configuration configuration;
private String id;
private Integer fetchSize;
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType;
private SqlSource sqlSource;
private Cache cache;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
private MappedStatement() {
// constructor disabled
}
//......其他略
}
看到这些类的字段是不是有些莫名的熟悉,想的起来吗?没事,我再截一张图。
这下知道了吧,就是Sql语句的信息,把xml配置解析成MappedStatement。
好,接着看创建SqlCommand,获取MappedStatement之后,接着就是把MappedStatement的id属性设置到SqlCommand的name属性中去,我们这里就是com.shandian.mapper.empmapper.queryall,其实她就是我们在mapper.xml中配置的<select>namespace + id,而type就是select.至此SqlCommand创建完成。
接下来继续看MethodSignature的创建过程,
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class<?>) {
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);
this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
this.returnsCursor = Cursor.class.equals(this.returnType);
this.mapKey = getMapKey(method);
this.returnsMap = (this.mapKey != null);
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
这里是得到这个方法的返回类型,参数类型等信息,然后依次判断是否返回Void,是否返回多条结果,是否返回Map,rowBound类型参数位置,参数信息等等。
至此 MapperMethod 创建获取完毕,获取MapperMethod时涉及到很多对象,写的也比较乱,那现在总结一下,MapperMethod是和数据库打交道的重要一个对象,它统领了和数据库打交道的方法。MapperMethod内部维护着SqlCommand对象和MethodSignature对象,这两个是它的内部类。SqlCommand,主要是封装了SQL标签的类型,和要执行那个SQL的id,而MethodSignature主要是封装了方法的参数信息和具体返回值类型信息,所以有了这个对象就可以知道本次对数据库的操作是一种什么样的操作(SELECT,INSERT,UPDATE,DELETE),能够找到某某mapper.xml的哪个SQL,可以知道执行这段SQL传入的参数信息,还有执行完这段SQL后的返回值是什么。
这一篇主要分析了 MapperMethod 的作用 以及 MapperMethod 的创建过程。下一篇我们接着讨论查询过程。
如果有读者发现我说的有不对的地方,还望大家多多指教!