MyBatis源码分析(下)
上篇文章我们主要分析了Mybatis源码前半部分流程中创建SqlSessionFactory,然后再创建SqlSession的源码,本篇文章就来分析后半部分流程的源码。
通过SqlSession获取对应Mapper代理对象
依旧是之前MyBatis标准用法的代码
1 String resource = "org/mybatis/example/mybatis-config.xml"; 2 3 InputStream inputStream = Resources.getResourceAsStream(resource); 4 5 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 6 7 SqlSession session = sqlSessionFactory.openSession(); 8 9 try { 10 11 BlogMapper mapper = session.getMapper(BlogMapper.class); 12 13 Blog blog = mapper.selectBlog(101); 14 15 } finally { 16 session.close(); 17 }
此时我们已经获得了SqlSession对象(对应实现类是 DefaultSqlSession),然后就执行第11行的代码,通过SqlSession的getMapper方法获取Mapper的代理对象。看下getMapper()方法的源码:
1 @Override 2 public <T> T getMapper(Class<T> type) { 3 return configuration.getMapper(type, this); 4 }
1 public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 2 return mapperRegistry.getMapper(type, sqlSession); 3 }
1 @SuppressWarnings("unchecked") 2 public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 3 final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); 4 if (mapperProxyFactory == null) { 5 throw new BindingException("Type " + type + " is not known to the MapperRegistry."); 6 } 7 try { 8 return mapperProxyFactory.newInstance(sqlSession); 9 } catch (Exception e) { 10 throw new BindingException("Error getting mapper instance. Cause: " + e, e); 11 } 12 }
可以看到这里将SqlSession作为参数,最终调用的是 MapperRegistry对象的 getMapper()方法。大家还记得上篇文章中有说过,在创建SqlSessionFactoryBuilder时,就会解析配置文件中的<mappers></mappers>指定的Mapper,并放入到 MapperRegistry对象的 knownMappers 这个 Map类型的属性中么?我们可以看到这里的第3~6行代码,就是在校验传入的Class对象在之前是否注册过,如果之前没有被加载,则认为本次传入的是一个非法的Mapper Class类,直接抛出异常。如果传入的Mapper Class 之前有加载过,就会返回一个 MapperProxyFactory对象,这个对象就存储了传入Mapper的类型和这个Mapper中缓存的方法,然后调用 newInstance()方法获取传入Mapper的代理对象。我们看下newInstance()方法干了什么事:
1 public T newInstance(SqlSession sqlSession) { 2 final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); 3 return newInstance(mapperProxy); 4 }
可以看到这里新建了一个具体传入Mapper类型的 MapperProxy对象,我们看下MapperProxy的源码:
1 public class MapperProxy<T> implements InvocationHandler, Serializable { 2 3 private static final long serialVersionUID = -6424540398559729838L; 4 private final SqlSession sqlSession; 5 private final Class<T> mapperInterface; 6 private final Map<Method, MapperMethod> methodCache; 7 8 public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) { 9 this.sqlSession = sqlSession; 10 this.mapperInterface = mapperInterface; 11 this.methodCache = methodCache; 12 } 13 14 @Override 15 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 16 try { 17 if (Object.class.equals(method.getDeclaringClass())) { 18 return method.invoke(this, args); 19 } else if (isDefaultMethod(method)) { 20 return invokeDefaultMethod(proxy, method, args); 21 } 22 } catch (Throwable t) { 23 throw ExceptionUtil.unwrapThrowable(t); 24 } 25 final MapperMethod mapperMethod = cachedMapperMethod(method); 26 return mapperMethod.execute(sqlSession, args); 27 } 28 29 private MapperMethod cachedMapperMethod(Method method) { 30 return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())); 31 } 32 33 private Object invokeDefaultMethod(Object proxy, Method method, Object[] args) 34 throws Throwable { 35 final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class 36 .getDeclaredConstructor(Class.class, int.class); 37 if (!constructor.isAccessible()) { 38 constructor.setAccessible(true); 39 } 40 final Class<?> declaringClass = method.getDeclaringClass(); 41 return constructor 42 .newInstance(declaringClass, 43 MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED 44 | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC) 45 .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args); 46 } 47 48 /** 49 * Backport of java.lang.reflect.Method#isDefault() 50 */ 51 private boolean isDefaultMethod(Method method) { 52 return (method.getModifiers() 53 & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC 54 && method.getDeclaringClass().isInterface(); 55 } 56 }
既然是一个Proxy代理类,那么我们肯定是看 invoke方法,看看它具体代理了些啥。
这里的17~20行先判断当前执行的方法是否为用户自己写的需要被代理的方法,如果当前调用的是 toString()、equals()这种继承于Object类的方法,则不需要被代理,直接调用即可。如果当前执行的方法需要被代理,则会执行第25行代码,先从缓存中看之前是否有执行过该方法,如果执行过就直接从缓存得到该方法的对象,没有执行过就新建一个该方法的对象并缓存起来。最后会执行第26行execute()方法,来看下源码:
1 public class MapperMethod { 2 3 private final SqlCommand command; 4 private final MethodSignature method; 5 6 public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { 7 this.command = new SqlCommand(config, mapperInterface, method); 8 this.method = new MethodSignature(config, mapperInterface, method); 9 } 10 11 public Object execute(SqlSession sqlSession, Object[] args) { 12 Object result; 13 switch (command.getType()) { 14 case INSERT: { 15 Object param = method.convertArgsToSqlCommandParam(args); 16 result = rowCountResult(sqlSession.insert(command.getName(), param)); 17 break; 18 } 19 case UPDATE: { 20 Object param = method.convertArgsToSqlCommandParam(args); 21 result = rowCountResult(sqlSession.update(command.getName(), param)); 22 break; 23 } 24 case DELETE: { 25 Object param = method.convertArgsToSqlCommandParam(args); 26 result = rowCountResult(sqlSession.delete(command.getName(), param)); 27 break; 28 } 29 case SELECT: 30 if (method.returnsVoid() && method.hasResultHandler()) { 31 executeWithResultHandler(sqlSession, args); 32 result = null; 33 } else if (method.returnsMany()) { 34 result = executeForMany(sqlSession, args); 35 } else if (method.returnsMap()) { 36 result = executeForMap(sqlSession, args); 37 } else if (method.returnsCursor()) { 38 result = executeForCursor(sqlSession, args); 39 } else { 40 Object param = method.convertArgsToSqlCommandParam(args); 41 result = sqlSession.selectOne(command.getName(), param); 42 if (method.returnsOptional() 43 && (result == null || !method.getReturnType().equals(result.getClass()))) { 44 result = Optional.ofNullable(result); 45 } 46 } 47 break; 48 case FLUSH: 49 result = sqlSession.flushStatements(); 50 break; 51 default: 52 throw new BindingException("Unknown execution method for: " + command.getName()); 53 } 54 if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { 55 throw new BindingException("Mapper method '" + command.getName() 56 + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); 57 } 58 return result; 59 } 60 }
可以看到MapperMethod有两个属性,类型分别为 SqlCommand、MethodSignature,分别存储的是该方法的完整sql语句和参数,以及方法的签名(比如所属的namespace和id,可以唯一定义方法)。然后进入execute()方法,可以看到首先判断当前执行方法是增删改查中的哪一种,我们这边简单起见,就直接看最简单的select方法(返回类型为MyBatis定义的简单数据类型)。定位到第40、41行,我们可以发现这里将参数设置到了sql语句中,然后执行了SqlSession对象的selectOne()方法。之前已经说了SqlSession中保存的就是执行器和一系列底层的增删改查方法。那么具体是怎么做的呢?我们接着分析。
通过SqlSession执行具体的sql操作
我们看一下 selectOne 方法的源码:
1 @Override 2 public <T> T selectOne(String statement, Object parameter) { 3 // Popular vote was to return null on 0 results and throw exception on too many. 4 List<T> list = this.selectList(statement, parameter); 5 if (list.size() == 1) { 6 return list.get(0); 7 } else if (list.size() > 1) { 8 throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); 9 } else { 10 return null; 11 } 12 }
1 @Override 2 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { 3 try { 4 MappedStatement ms = configuration.getMappedStatement(statement); 5 return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); 6 } catch (Exception e) { 7 throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); 8 } finally { 9 ErrorContext.instance().reset(); 10 } 11 }
可以看到这里实际调用的是 selectList()方法,然后返回第一个结果。selectList()方法的第4行先获取了当前执行sql的所需对象(即MappedStatement 包括namespace+方法名称+参数个数及类型+返回对象及类型+具体执行的sql语句,在刚开始构建SqlSesstionFactoryBuilder时就已经设置到Configuration对象的 MappedStatement这个Map中),最后调用 执行器(之前说过默认的执行器为 SimpleExecutor)的query方法执行sql语句,源码如下:
1 @Override 2 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { 3 BoundSql boundSql = ms.getBoundSql(parameter); 4 CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); 5 return query(ms, parameter, rowBounds, resultHandler, key, boundSql); 6 } 7 8 @SuppressWarnings("unchecked") 9 @Override 10 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { 11 ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); 12 if (closed) { 13 throw new ExecutorException("Executor was closed."); 14 } 15 if (queryStack == 0 && ms.isFlushCacheRequired()) { 16 clearLocalCache(); 17 } 18 List<E> list; 19 try { 20 queryStack++; 21 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; 22 if (list != null) { 23 handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); 24 } else { 25 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); 26 } 27 } finally { 28 queryStack--; 29 } 30 if (queryStack == 0) { 31 for (DeferredLoad deferredLoad : deferredLoads) { 32 deferredLoad.load(); 33 } 34 // issue #601 35 deferredLoads.clear(); 36 if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { 37 // issue #482 38 clearLocalCache(); 39 } 40 } 41 return list; 42 }
可以看到这里最终执行的是下面的这个 query()方法,这里大致就是先判断当前查询是否为强制从数据库中去数据的操作(设置FLUSH为true),如果确实之前的sql操作已经全部执行完成(保存query操作的栈中没有对象),那就将 localCache(即基于Session的一级缓存)给清空,然后将本次的查询操作放入 保存query操作的栈中(为执行失败时,之后还要再执行提供方便),然后就到了第21行,这里会先从localCache取数据,如果一级缓存中有数据则直接返回,如果没有缓存则执行第25行的 queryFromDatabase()方法,我们看下这个方法的源码:
1 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { 2 List<E> list; 3 localCache.putObject(key, EXECUTION_PLACEHOLDER); 4 try { 5 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); 6 } finally { 7 localCache.removeObject(key); 8 } 9 localCache.putObject(key, list); 10 if (ms.getStatementType() == StatementType.CALLABLE) { 11 localOutputParameterCache.putObject(key, parameter); 12 } 13 return list; 14 }
可以看到在执行前先往localCache存放一个占位符,然后执行sql操作,最后把sql执行完返回的对象存到list变量中,删除之前的占位缓存并放入新缓存(缓存的是list对象)。我们来看下doQuery做了些什么:
1 @Override 2 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { 3 Statement stmt = null; 4 try { 5 Configuration configuration = ms.getConfiguration(); 6 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); 7 stmt = prepareStatement(handler, ms.getStatementLog()); 8 return handler.query(stmt, resultHandler); 9 } finally { 10 closeStatement(stmt); 11 } 12 }
1 @Override 2 public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { 3 PreparedStatement ps = (PreparedStatement) statement; 4 ps.execute(); 5 return resultSetHandler.handleResultSets(ps); 6 }
到这里就非常清楚了,其实SimpleExecutor底层的doQuery()方法最后就是 new了一个 PrepareStatement预编译sql对象,然后调用了execute()方法,就是我们以前最常用的JDBC原生操作。第5行还有个将执行结果用 resultSetHandler去处理的操作,其实就是讲sql返回的结果一个个去绑定到List<E> 这个E类型的属性中,感兴趣的同学可以自己去跟下源码。
OK,到这里一次Mybatis执行sql的完整流程就已经全部讲解完毕了。其实MyBatis的源码相对现在比较流行的框架和组件比如Spring、Netty、MQ等等来讲已经是很简单了,非常适合第一次读源码的同学好好参透。其实我们看源码最终的目的是要理解它的整个设计架构有什么好处,为什么要这么设计,也就是所谓的要了解设计的本质思想。当了解这一点后才算是真正通读了源码,之后才能运用到实际项目中去。文章到这里就结束了,之后我会抽时间更新一下MyBatis源码中使用到的一些设计模式,和大家一起来了解下源码各部分设计的精髓。
博客的下一个主题也已经定下了,就是和大家分享Spring各组件的源码设计(包括IOC,AOP,Transaction等等),希望大家能从我的博文中学到知识,这才是我写博客最重要的目的。好的,那我们下个主题见啦~

浙公网安备 33010602011771号