我们在前面Mybatis源码解析之一级缓存 介绍过使用SqlSession提供的方法直接操作JDBC的方式。
在实际工作中,我们一般都比较偏向于使用以下方式来进行JDBC操作
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog3 = mapper.queryById(17);
以前一直比较奇怪一个问题,就是BlogMapper是一个接口,按照我们的一般思路应该是有一个实现类,才能继续操作其中的方法,但这个是接口类,所以肯定有一个默认的实现类来操作了,具体是如何的呢?
1.Mybatis的使用
具体配置可参考Mybatis源码解析之一级缓存
2.测试类
public static void main(String[] args) {
SqlSession session = sqlSessionFactory.openSession();
SqlSession session1 = sqlSessionFactory.openSession();
try {
// 直接获取对应的Mapper
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog3 = mapper.queryById(17);
} finally {
session.close();
}
}
结果:这样是可以测试成功的,使用同session.selectOne()是一致的
3.有关于SqlSession.getMapper()的源码分析 1)DefaultSqlSession.getMapper()默认实现类为DefaultSqlSession
// DefaultSqlSession.getMapper()
@Override
public T getMapper(Class type) {
return configuration.getMapper(type, this);
}
// Configuration.getMapper()
public T getMapper(Class type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
// MapperRegistry.getMapper()
@SuppressWarnings("unchecked")
public T getMapper(Class type, SqlSession sqlSession) {
// 1.可以看到,这里是从MapperRegistry.knownMappers获取对应的MapperProxyFactory
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 2.从MapperProxyFactory中获取MapperProxy
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
2)MapperProxyFactory.newInstance()获取MapperProxy
public T newInstance(SqlSession sqlSession) {
final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
可以看到,这个是一个动态代理模式,我们使用MapperProxy来代理所有的类(在本例中就是BlogMapper接口)
总结:SqlSession.getMapper()获取的就是一个代理类MapperProxy
Q:
这里有一个问题,MapperRegistry.getMapper()获取MapperProxy,而MapperProxy又是从MapperProxyFactory中获取的,MapperProxyFactory从knownMappers.get(type)获取,这个knownMappers什么时候把type和MapperProxyFactory的值封装进来的呢?
这里先当做一个疑问,读者先思考一下!
4.BlogMapper.queryById()的执行分析
由上可知:真正执行的是代理类MapperProxy,那么执行方法时,就会执行MapperProxy.invoke()方法
1)MapperProxy.invoke()// MapperProxy.invoke()
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Object的方法执行
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
// 获取MapperMethod
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;
}
2)MapperMethod.execute()方法执行
// MapperMethod.execute()
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 具体的增删改查操作,都有具体的执行,
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
// 在这里我们主要看本例中的查询操作
} else if (SqlCommandType.SELECT == command.getType()) {
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
// 本例就返回一个结果值,看这里
// 封装参数值,最后还是交给SqlSession来处理
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else if (SqlCommandType.FLUSH == command.getType()) {
result = sqlSession.flushStatements();
} else {
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
5.MapperProxyFactory的封装与获取
MapperProxy的执行过程我们看完了,现在回过头来,看下我们刚才提的问题,MapperProxyFactory是什么时候和type对应封装起来的呢?
仔细回忆整个过程,除了获取BlogMapper,整个测试代码就只有解析blog.xml,那么就应该是在解析blog.xml的时候把这个对应关系封装起来的。那么我们就从这里开始入手
1)SqlSessionFactoryBuilder.parse()
还是从这里开始追踪,按照之前的追踪过程,我们一路追到XMLMapperBuilder.parse()
// XMLMapperBuilder.parse()
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));// 之前分析过这个方法
configuration.addLoadedResource(resource);
bindMapperForNamespace();// 我们来看下这个方法
}
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}
// bindMapperForNamespace()
private void bindMapperForNamespace() {
// 1.这里的namespace就是blog.xml中配置的mapper对应的namespace,也就是mybatis.BlogMapper
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class boundType = null;
try {
// 2.反射获取对应的type
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
configuration.addLoadedResource("namespace:" + namespace);
// 3.这里就是将BlogMapper添加到configuration,我们来具体看一下做法
configuration.addMapper(boundType);
}
}
}
}
// configuration.addMapper()
public void addMapper(Class type) {
mapperRegistry.addMapper(type);// 继续看
}
// mapperRegistry.addMapper()
public void addMapper(Class type) {
// 1.只有接口才添加..有点意思啊
if (type.isInterface()) {
// 2.在这里我们可以看到,Mapper名称是不能重复的,否则会报错
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 3.将type和MapperProxyFactory的对应关系添加到knowMappers中
// 就是这里喽
knownMappers.put(type, new MapperProxyFactory(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
总结:我们一次性看完解析的所有操作,最终找到,在解析mapper xml文件的时候,把具体的Mapper添加到Configuration中,
总结:
1)解析xml文件时,将Mapper type和MapperProxyFactory的对应关系封装到Configuration中,以备后面使用
2)在SqlSession.getMapper(type)时,就是从Configuration中获取type对应的MapperProxyFactory
3)MapperProxyFactory用于获取MapperProxy
4)MapperProxy是一个动态代理类,真正的实现还是交由SqlSession来处理