MyBatis源码阅读

编程式开发使用MyBatis

在研究MyBatis源码之前,先来看下单独使用MyBatis来查询数据库时是怎么做的:

1 InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
2 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
3 SqlSession sqlSession = sqlSessionFactory.openSession();
4 BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
5 Blog blog = blogMapper.selectByBlogId("1");
6 System.out.println(blog.toString());

第一步:把全局配置文件读取成流;

第二步:SqlSessionFactoryBuilder根据配置文件构建SqlSessionFactory;

第三步:SqlSessionFactory通过调用openSession()方法创建会话SqlSession;

第四步:SqlSession调用getMapper方法获取Mapper接口对象;

第五步:Mapper接口对象执行查询。

此处,我们把文件读取成流的步骤就省略了,直接从第二步构建SqlSessionFactory工厂开始解析源码。

1、构建SqlSessionFactory工厂

MyBatis通过建造者模式创建一个工厂,配置文件的解析就是在这一步完成的,包括mybatis-config.xml和Mapper.xml文件。

(1)配置解析过程

从上面的代码中可以看到:

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

首先new了一个SqlSessionFactoryBuilder,非常明显的建造者模式,它里面定义了很多个build方法的重载,最终返回的是一个SqlSessionFactory对象(单例模式)。

我们看下build方法源码:

 1 public SqlSessionFactory build(InputStream inputStream) {
 2     return build(inputStream, null, null);
 3 }
 4   
 5 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
 6     try {
 7       XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
 8       return build(parser.parse());
 9     } catch (Exception e) {
10       throw ExceptionFactory.wrapException("Error building SqlSession.", e);
11     } finally {
12       ErrorContext.instance().reset();
13       try {
14         inputStream.close();
15       } catch (IOException e) {
16         // Intentionally ignore. Prefer previous error.
17       }
18     }
19 }
View Code

这里面创建了一个XMLConfigBuilder对象(Configuration对象也是这个时候创建的)。

XMLConfigBuilder是抽象类BaseBuilder的一个子类,专门用来解析全局配置文件,针对不同的构建目标还有其他的一些子类,比如:XMLMapperBuilder用来解析Mapper映射器,XMLStatementBuilder用来解析增删改查标签。

XMLConfigBuilder先执行parse方法解析xml文件,返回一个Configuration对象:

1 public Configuration parse() {
2     if (parsed) {
3       throw new BuilderException("Each XMLConfigBuilder can only be used once.");
4     }
5     parsed = true;
6     parseConfiguration(parser.evalNode("/configuration"));
7     return configuration;
8 }
View Code

配置文件里面所有的信息都会放在Configuration里面。Configuration类里面有很多属性,有很多是跟config里面的标签直接对应的。

然后执行SqlSessionFactoryBuilder建造者的build方法,把根据配置文件生成的Configuration对象传进去:

1 public SqlSessionFactory build(Configuration config) {
2     return new DefaultSqlSessionFactory(config);
3 }

可以看到,直接返回一个默认工厂DefaultSqlSessionFactory对象。

我们重点关注解析配置文件到Configuration对象中的parse方法:

看源码知道,parse方法会先检查是不是已经解析过,也就是说在应用的生命周期里面,config配置文件只需要解析一次,生成的Configuration对象也会存在应用的整个声明周期。接下来就是parseConfiguration方法:

 1 private void parseConfiguration(XNode root) {
 2     try {
 3       //issue #117 read properties first
 4       propertiesElement(root.evalNode("properties"));
 5       Properties settings = settingsAsProperties(root.evalNode("settings"));
 6       loadCustomVfs(settings);
 7       loadCustomLogImpl(settings);
 8       typeAliasesElement(root.evalNode("typeAliases"));
 9       pluginElement(root.evalNode("plugins"));
10       objectFactoryElement(root.evalNode("objectFactory"));
11       objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
12       reflectorFactoryElement(root.evalNode("reflectorFactory"));
13       settingsElement(settings);
14       // read it after objectFactory and objectWrapperFactory issue #631
15       environmentsElement(root.evalNode("environments"));
16       databaseIdProviderElement(root.evalNode("databaseIdProvider"));
17       typeHandlerElement(root.evalNode("typeHandlers"));
18       mapperElement(root.evalNode("mappers"));
19     } catch (Exception e) {
20       throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
21     }
22 }
View Code

这里面有十几个方法,对应着config文件里面的所有一级标签。下面对这些方法做一下简要说明:

 propertiesElement()

解析properties标签,读取我们引入的外部配置文件。这里有两种类型,一种是放在resource目录下的,是相对路径,一种是写的绝对路径。解析的最终结果就是我们会把所有的配置信息放到名为defaults的Properties对象里面,最后把XPathParser和Configuration的Properties属性都设置成我们填充后的Properties对象。

settingsAsProperties()

把settings标签也解析成了Properties对象,对于<settings>的子标签的处理在后面。之所以这里先将settings标签解析成Properties对象,是因为下面两个方法要用。

loadCustomVfs(settings)

loadCustomVfs是获取Vitual File System的自定义实现类,比如我们要读取本地文件,或者FTP远程文件的时候,就可以用到自定义的VFS类。我们根据<settings>标签里面的<vfsImpl>标签,生成一个抽象类VFS的子类,并且赋值到Configuration中。

loadCustomLogImpl(settings)

loadCustomLogImpl是根据<logImpl>标签获取日志的实现类,我们可以用到很多的日志的方案,包括log4j、log4j2、slf4j等,这里生成一个Log接口的实现类,并且赋值到Configuration中。

typeAliasesElement()

接下来,解析<typeAliases>标签,它有两种定义方式,一种是直接定义一个类的别名,一种就是指定一个包,那么这个package下面所有的类的名字就会成为这个类全路径的别名。类的别名和类的关系,放在一个TypeAliasRegistry对象里面。

pluginElement()

接下来解析的是<plugins>标签,比如pageHelper的翻页插件,或者我们自定义的插件。<plugins>标签里面只有<plugin>标签,<plugin>标签里面只有<property>标签。标签解析完以后,会生成一个Interceptor对象,并且添加到Configuration的InterceptorChain属性里面,它是一个List。

objectFactoryElement()、objectWrapperFactoryElement()

这两个标签是用来实例化对象用的,<objectFactory>和<objectWrapperFactory>这两个标签,分别生成ObjectFactory、ObjectWrapperFactory对象,同样设置到Configuration属性里面。

reflectorFactory()

解析<reflectorFactory>标签,生成ReflectorFactory对象。

settingsElement(settings)

这里就是对<settings>标签里面所有子标签的处理了,前面已经把子标签全部转换成了Properties对象,所以在这里处理Properties对象就可以了。二级标签里面有很多的配置,比如二级缓存,延迟加载,自动生成主键这些。需要注意的是,这些标签的所有的默认值都是在这里赋值的。所有的值都会赋值到Configuration对象里去。

environmentsElement()

这一步解析<environments>标签。我们知道,一个environment对应一个数据源,所以在这里我们会根据配置的<transactionManager>创建一个事务工厂,根据<dataSource>标签创建一个数据源,最后把这两个对象设置成Environment对象的属性,放到Configuration里面。

databaseIdProviderElement()

解析<databaseIdProvider>标签,生成DatabaseIdProvider对象(用来支持不同厂商的数据库)。

typeHandlerElement()

跟typeAlias一样,TypeHandler有两种配置方式,一种是单独配置一个类,一种是指定一个package。最后我们得到的是JavaType和JdbcType,以及用来做相互映射的TypeHandler之间的映射关系。最后放在TypeHandlerRegistry对象里面。

问题:这种三个对象(Java类型,JDBC类型,Handler)的关系怎么映射?(Map里面再放一个Map)

mapperElement()

http://www.mybatis.org/mybatis-3/zh/configuration.html#mappers

(1)判断

最后就是Mapper标签的解析,<mapper>标签的扫描类型:

扫描类型 含义
resource 相对路径
url 绝对路径
class 单个Mapper接口
package

 

 

 

 

(2)注册

XMLMapperBuilder.parse()方法是对Mapper映射器的解析。里面有两个方法:

configurationElement()--解析所有的子标签,其中buildStatementFromContext()最终获得MappedStatement对象。

bindMapperForNamespace--把namespace(接口类型)和工厂类绑定起来。

无论是按package扫描还是按接口扫描,最后都会用到MapperRegistry的addMapper()方法。MapperRegistry里面维护的其实是一个Map,key是接口类型,value是一个MapperProxyFactory对象。

(3)处理注解

除了映射器文件,在这里也会去解析Mapper接口方法上的注解,在addMapper()方法里面创建了一个MapperAnnotationBuilder,进去看一下它的parse()方法:

 1 public void parse() {
 2     String resource = type.toString();
 3     if (!configuration.isResourceLoaded(resource)) {
 4       loadXmlResource();
 5       configuration.addLoadedResource(resource);
 6       assistant.setCurrentNamespace(type.getName());
 7       parseCache();
 8       parseCacheRef();
 9       Method[] methods = type.getMethods();
10       for (Method method : methods) {
11         try {
12           // issue #237
13           if (!method.isBridge()) {
14             parseStatement(method);
15           }
16         } catch (IncompleteElementException e) {
17           configuration.addIncompleteMethod(new MethodResolver(this, method));
18         }
19       }
20     }
21     parsePendingMethods();
22 }
View Code

 

parseCache()和parseCacheRef()方法其实是对@CacheNamespace和@CacheNamespaceRef这两个注解的处理。parseStatement()方法里面的各种getAnnotation(),都是对注解的解析,比如@Options,@SelectKey,@ResultMap等。最后同样会解析成MappedStatement对象,也就是说在XML中配置,和使用注解配置,最后起到的效果是一样的。

(4)收尾

如果注册没有完成,还要从Map里面remove掉:

1 finally {
2     if (!loadCompleted) {
3         knownMappers.remove(type);
4     }
5 }

 

最后,MapperRegistry也会放到Configuration中去。

最后调用SqlSessionFactoryBuilder自己的build(configuration)方法,返回DefaultSqlSessionFactory。

总结:这一步,主要完成了config配置文件、Mapper文件、Mapper接口上注解的解析,得到一个最重要的Configuration对象,这里面存储了全部的配置信息,它的属性里面还有各种各样的容器。最后返回一个DefaultSqlSessionFactory,里面持有了Configuration实例。

2、创建SqlSession会话

 我们跟数据库的每一次连接,都需要创建一个会话,我们用openSession()方法来创建:

 1 public SqlSession openSession() {
 2     return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
 3 }
 4   
 5 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
 6     Transaction tx = null;
 7     try {
 8       final Environment environment = configuration.getEnvironment();
 9       final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
10       tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
11       final Executor executor = configuration.newExecutor(tx, execType);
12       return new DefaultSqlSession(configuration, executor, autoCommit);
13     } catch (Exception e) {
14       closeTransaction(tx); // may have fetched a connection so lets call close()
15       throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
16     } finally {
17       ErrorContext.instance().reset();
18     }
19 }
View Code

 

DefaultSqlSessionFactory.openSessionFromDataSource()这个会话里面,需要包含一个Executor用来执行SQL。Executor又要指定事务类型和执行器的类型。所以我们会先从Configuration里面拿到Environment,Environment里面就有事务工厂。

(1)创建Transaction

属性 产生工厂类 产生事务
JDBC JdbcTransactionFactory JdbcTransaction
MANAGED ManagedTransactionFactory ManagedTransaction

 

 

如果配置的是JDBC,则会使用Connection对象的commit()、rollback()、close()管理事务。

如果配置成MANAGED,会把事务交给容器来管理,比如JBOSS、Weblogic。因为我们跑的是本地程序,如果配置成MANAGED不会有任何事务。

如果是Spring+MyBatis,则没有必要配置,因为我们会直接在applicationContext.xml里面配置数据源和事务管理器,覆盖MyBatis的配置。

(2)创建Executor

Executor的基本类型有三种:SIMPLE、BATCH、REUSE,默认是SIMPLE(settingsElement读取默认值),它们都继承了抽象类BaseExecutor。

为什么要让抽象类实现Executor接口,然后让具体实现类继承抽象类?(模板方法模式,父类定义算法骨架,子类可以重新定义算法)

三种类型的区别:

类型 说明
SimpleExecutor 每执行一次select或update,就开启一个Statement对象,用完立刻关闭Statement对象。
ReuseExecutor 执行select或update,以sql作为key查找Statement对象, 存在就是用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。就是重复使用Statement
BatchExecutor 执行update,将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理,与JDBC批处理相同。

 

 

 

 

如果配置了cacheEnabled=true,会用装饰器模式对Executor进行包装:new CachingExecutor(executor)。包装完毕后,会执行:executor = (Executor) interceptorChain.pluginAll(executor);此处会对executor进行包装。

最终返回DefaultSqlSession,属性包括Configuration、Executor对象。

总结:创建会话的过程,我们获得了一个DefaultSqlSession,里面包含了一个Executor,它是SQL的执行者。

3、获取Mapper接口

 现在我们有一个DefaultSqlSession了,必须找到Mapper.xml里面定义的StatementId,才能执行对应的SQL语句。

找到StatementId有两种方式:一种是直接调用session的方法,在参数里面传入statementId,这种方式属于硬编码,我们没办法知道有多少处调用,修改起来也很麻烦。而且如果参数传入错误,在编译阶段是不会报错的,不利于预先发现问题。例如:

Blog blog = (Blog) session.selectOne("com.test.mapper.BlogMapper.selectBlogById", 1);

 

所以,MyBatis后期的版本提供了第二种方式,就是定义一个接口,然后再调用Mapper接口的方法。由于我们的接口名称跟Mapper.xml的namespace是对应的,接口的方法跟statementId也都是对应的,所以根据方法就能找到对应的要执行的SQL。

BlogMapper blogMapper = session.getMapper(BlogMapper.class);

 

在这里我们主要来研究一下Mapper接口对象是如何获得的,它的本质是什么。

DefaultSqlSession的getMapper()方法,直接调用的Configuration的getMapper()方法:

1 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
2     return mapperRegistry.getMapper(type, sqlSession);
3 }

 

Configuration的getMapper()又是直接调用的MapperRegistry的getMapper()方法:

1 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
2     return mapperRegistry.getMapper(type, sqlSession);
3 }

 

MapperRegistry的getMapper()方法:

 1 private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
 2 
 3 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
 4     final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
 5     if (mapperProxyFactory == null) {
 6       throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
 7     }
 8     try {
 9       return mapperProxyFactory.newInstance(sqlSession);
10     } catch (Exception e) {
11       throw new BindingException("Error getting mapper instance. Cause: " + e, e);
12     }
13 }

 

我们知道,在解析mapper标签和Mapper.xml的时候已经把接口类型和类型对应的MapperProxyFactory放到一个Map中。获取Mapper代理对象,实际上是从Map中获取对应的工厂类后,调用mapperProxyFactory.newInstance(sqlSession)方法创建对象。最终通过代理模式返回代理对象:

1 public T newInstance(SqlSession sqlSession) {
2     final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
3     return newInstance(mapperProxy);
4 }
5 
6 protected T newInstance(MapperProxy<T> mapperProxy) {
7     return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
8 }

 

这也是为什么Mapper接口没有实现类也能执行SQL语句的原因:我们只需要接口类型+方法名就可以找到StatementId了,而唯一要做的一件事也是这样,所以不需要实现类。在MapperProxy里面直接执行逻辑(也就是执行SQL)就可以。

总结:获取Mapper对象的过程,实质上是获取了一个MapperProxy的代理对象。MapperProxy中有sqlSession、mapperInterface、methodCache。

4、调用接口方法执行SQL

 由于所有的Mapper都是代理对象MapperProxy,所以我们执行Mapper的任意方法,都是执行的MapperProxy的invoke()方法:

 1 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 2     try {
 3       if (Object.class.equals(method.getDeclaringClass())) {
 4         return method.invoke(this, args);
 5       } else if (isDefaultMethod(method)) {
 6         return invokeDefaultMethod(proxy, method, args);
 7       }
 8     } catch (Throwable t) {
 9       throw ExceptionUtil.unwrapThrowable(t);
10     }
11     final MapperMethod mapperMethod = cachedMapperMethod(method);
12     return mapperMethod.execute(sqlSession, args);
13 }
View Code

(1)首先判断是否需要执行SQL,还是直接执行方法。Object本身的方法和Java8中接口的默认方法不需要去执行SQL。

(2)获取缓存。这里加入缓存是为了提升MapperMethod的获取速度:

1 private MapperMethod cachedMapperMethod(Method method) {
2     return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
3   }
View Code

 

Map的computeIfAbsent()方法:只有key不存在或者value为null的时候才调用mappingFuction()。

最后执行MapperMethod的execute()方法。MapperMethod里面主要有两个属性,一个是SqlCommand,一个是MethodSignature,这两个都是MapperMethod的内部类。另外,还定义了多个execute方法:

下面是execute()方法源码:

 1 public Object execute(SqlSession sqlSession, Object[] args) {
 2     Object result;
 3     switch (command.getType()) {
 4       case INSERT: {
 5         Object param = method.convertArgsToSqlCommandParam(args);
 6         result = rowCountResult(sqlSession.insert(command.getName(), param));
 7         break;
 8       }
 9       case UPDATE: {
10         Object param = method.convertArgsToSqlCommandParam(args);
11         result = rowCountResult(sqlSession.update(command.getName(), param));
12         break;
13       }
14       case DELETE: {
15         Object param = method.convertArgsToSqlCommandParam(args);
16         result = rowCountResult(sqlSession.delete(command.getName(), param));
17         break;
18       }
19       case SELECT:
20         if (method.returnsVoid() && method.hasResultHandler()) {
21           executeWithResultHandler(sqlSession, args);
22           result = null;
23         } else if (method.returnsMany()) {
24           result = executeForMany(sqlSession, args);
25         } else if (method.returnsMap()) {
26           result = executeForMap(sqlSession, args);
27         } else if (method.returnsCursor()) {
28           result = executeForCursor(sqlSession, args);
29         } else {
30           Object param = method.convertArgsToSqlCommandParam(args);
31           result = sqlSession.selectOne(command.getName(), param);
32           if (method.returnsOptional() &&
33               (result == null || !method.getReturnType().equals(result.getClass()))) {
34             result = Optional.ofNullable(result);
35           }
36         }
37         break;
38       case FLUSH:
39         result = sqlSession.flushStatements();
40         break;
41       default:
42         throw new BindingException("Unknown execution method for: " + command.getName());
43     }
44     if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
45       throw new BindingException("Mapper method '" + command.getName()
46           + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
47     }
48     return result;
49 }
View Code

 

这一步,根据不同的type和返回类型,调用convertArgsToSqlCommandParam()将参数转换为SQL的参数, 然后调用SqlSession的insert()、update()、delete()、selectList()方法。

这里我们主要看下selectList的源码:

 1 public <E> List<E> selectList(String statement, Object parameter) {
 2     return this.selectList(statement, parameter, RowBounds.DEFAULT);
 3 }
 4   
 5 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
 6     try {
 7       MappedStatement ms = configuration.getMappedStatement(statement);
 8       return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
 9     } catch (Exception e) {
10       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
11     } finally {
12       ErrorContext.instance().reset();
13     }
14 }
View Code

 

首先,根据command name(StatementId)从Configuration中拿到MappedStatement,这个ms里面有我们在xml中配置的所有属性,包括id、statementType、sqlSource、useCache、入参、出参等等。

然后,执行executor的query()方法。我们上面说过,Executor有三种基本类型,SimpleExecutor、ReuseExecutor、BatchExecutor,还有一种包装器类型CachingExecutor。那么在这里会选择哪一种执行器呢?我们要回过头去看看DefaultSqlSession在初始化的时候是怎么赋值的,就是创建会话的过程。如果启用了二级缓存,就会先调用CachingExecutor的query()方法,里面有缓存相关的操作,然后才是再调用基本类型的执行器,比如默认的SimpleExecutor。

在没有开启二级缓存的情况下,会先走到BaseExecutor的query()方法:

1 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
2     BoundSql boundSql = ms.getBoundSql(parameter);
3     CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
4     return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
5 }
View Code

 

首先获取CacheKey。从Configuration中获取MappedStatement,然后从BoundSql中获取SQL信息,创建CacheKey,这个CacheKey就是缓存的key。然后再调用另一个query()方法:

 1 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
 2     ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
 3     if (closed) {
 4       throw new ExecutorException("Executor was closed.");
 5     }
 6     if (queryStack == 0 && ms.isFlushCacheRequired()) {
 7       clearLocalCache();
 8     }
 9     List<E> list;
10     try {
11       queryStack++;
12       list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
13       if (list != null) {
14         handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
15       } else {
16         list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
17       }
18     } finally {
19       queryStack--;
20     }
21     if (queryStack == 0) {
22       for (DeferredLoad deferredLoad : deferredLoads) {
23         deferredLoad.load();
24       }
25       // issue #601
26       deferredLoads.clear();
27       if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
28         // issue #482
29         clearLocalCache();
30       }
31     }
32     return list;
33 }
View Code

 

 queryStack用来记录查询栈,防止递归查询重复处理缓存。flushCache=true的时候(在<select>标签中配置),会先清理本地缓存(一级缓存):clearLocalCache()。如果没有缓存,会从数据库查询:queryFromDatabase()。如果LocalCacheScope==STATEMENT,会清理本地缓存。

我们来看下查询数据库的源码:

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

 

首先在缓存用占位符占位,执行查询后,移除占位符,放入数据。 然后执行Executor的doQuery(),默认是SimpleExecutor:

 1 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
 2     Statement stmt = null;
 3     try {
 4       Configuration configuration = ms.getConfiguration();
 5       StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
 6       stmt = prepareStatement(handler, ms.getStatementLog());
 7       return handler.query(stmt, resultHandler);
 8     } finally {
 9       closeStatement(stmt);
10     }
11 }
View Code

 

 首先,Configuration调用newStatementHandler()创建StatementHandler,得到RoutingStatementHandler。RoutingStatementHandler里面没有任何实现,是用来创建基本的StatementHandler的。这里会根据MappedStatement里面的statementType决定StatementType的类型。默认是PREPARED(STATEMENT、PREPARED、CALLABLE):

 1 public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
 2 
 3     switch (ms.getStatementType()) {
 4       case STATEMENT:
 5         delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
 6         break;
 7       case PREPARED:
 8         delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
 9         break;
10       case CALLABLE:
11         delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
12         break;
13       default:
14         throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
15     }
16 
17 }
View Code

 

 StatementHandler里面包含了处理参数的ParameterHandler和处理结果集的ResultSetHandler。这两个对象都是在上面new的时候创建的:

 1 public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
 2     super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
 3 }
 4 
 5 protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
 6     this.configuration = mappedStatement.getConfiguration();
 7     this.executor = executor;
 8     this.mappedStatement = mappedStatement;
 9     this.rowBounds = rowBounds;
10 
11     this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
12     this.objectFactory = configuration.getObjectFactory();
13 
14     if (boundSql == null) { // issue #435, get the key before calculating the statement
15       generateKeys(parameterObject);
16       boundSql = mappedStatement.getBoundSql(parameterObject);
17     }
18 
19     this.boundSql = boundSql;
20 
21     this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
22     this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
23 }
View Code

 

这三个对象都是可以被插件拦截的四大对象之一,所以在创建之后都要用拦截器进行包装的方法。

statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);

 

然后,用new出来的StatementHandler创建Statement对象--PreparedStatement()方法对语句进行预编译,处理参数:handler.parameterize(stmt) ; 

然后,执行StatementHandler的query()方法。通过前两步我们知道,Configuration对象new出来的StatementHandler是RoutingStatementHandler,所以,这里调用的是RoutingStatementHandler的query()方法:

1 private final StatementHandler delegate;
2 
3 public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
4     return delegate.<E>query(statement, resultHandler);
5 }
View Code

 

delegate委派,最终执行PreparedStatementHandler的query()方法:

1 public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
2     PreparedStatement ps = (PreparedStatement) statement;
3     ps.execute();
4     return resultSetHandler.handleResultSets(ps);
5 }
View Code

 

PreparedStatement的execute()后面就是JDBC包中的PreparedStatement的执行了。 

最后,ResultSetHandler处理结果集:return resultSetHandler.handleResultSets(ps);

ResultSetHandler 只有一个实现类: DefaultResultSetHandler 。也就是执行DefaultResultSetHandler 的handleResultSets ()方法。首先我们会先拿到第一个结果集,如果没有配置一个查询返回多个结果集的情况,一般只有一个结果集。如果下面的这个while 循环我们也不用,就是执行一次。然后会调用handleResultSet()方法。

posted @ 2019-12-01 17:57  Binger阳阳  阅读(341)  评论(0)    收藏  举报