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 }
View Code

 

既然是一个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 }
View Code

可以看到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   }
View Code

可以看到这里最终执行的是下面的这个 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等等),希望大家能从我的博文中学到知识,这才是我写博客最重要的目的。好的,那我们下个主题见啦~

 

posted @ 2019-04-20 22:03  ohbfskfhl  阅读(144)  评论(0)    收藏  举报