要分析MyBatis源码,我们还是从编程式用例入手,
public void testSelect() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession(); // ExecutorType.BATCH
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlogById(1);
System.out.println(blog);
} finally {
session.close();
}
}
把文件读取成流的这一步我们就省略了。所以下面我们分成四步来分析。
第一步,我们通过建造者模式创建一个工厂类,配置文件的解析就是在这一步完成的,包括mybatis-config.xml和Mapper适配器文件。
问题:解析的时候怎么解析的?做了什么?产生了什么对象?结果存放到哪里?解析的结果决定着我们后面有哪些对象可以使用,和到哪里使用
第二步,通过SqlSessionFactory创建一个SqlSession
问题:SqlSession是用来操作数据库的,返回了什么实现类?除了SqlSession,还创建了什么对象?
第三步,获得一个Mapper对象
问题:Mapper是一个接口,没有实现类,是不能被实例化的,那获取到的这个Mapper对象是什么对象?为什么要从SqlSession里面获取?为什么传进去一个接口,然后还要用接口类型来接收?
第四步, 调用接口方法
问题:我们的接口没有实现类,为什么可以调用它的方法?那它调用的是什么方法?它又是根据什么找到我们要执行的SQL的?也就是接口方法是怎么和xml映射器里面的Statement ID关联起来的?
此外,我们的方法参数是怎么转换成SQL参数的?获取到的结果集是怎么转换成对象的?
带着上面这些问题,接下来就详细分析每一步流程,包括里面有哪些核心的对象和关键的方法。
一、配置解析过程
首先我们要清楚的是配置解析过程只解析两种文件,一个是mybatis-config.xml全局配置文件,另外就是可能会有很多的Mapper.xml文件,也就是包括在Mapper接口类上面的注解。
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
首先我们new了一个SqlSessionFactoryBuilder,非常明显的建造者模式,它里面定义了很多个build方法的重载,最终返回的是SqlSessionFactory对象(单例模式)。我们点进build方法:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
这里面创建了一个 XMLConfigBuilder 对象(Configuration 对象也是这个时候创建的)。XMLConfigBuilder是抽象类BaseBuilder的一个子类,专门用来解析全局配置文件的。针对不同的构造目标,还有其他一些子类,比如:
XMLMapperBuilder: 解析Mapper映射器
XMLStatementBuilder: 解析增删改查标签
根据我们解析的文件流,创建了一个parser。然后调用parser的parser()方法,返回了一个Configuration对象,配置文件中所有的信息都会放在Configuration里面,Configuration类里面有很多的属性,有很多是跟config里面的标签直接对应的。我们先看一下parser方法:
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
首先判断是否已经被解析过了,如果已经解析过了,就抛exception。也就是说在应用的生命周期里面,config配置文件只需要解析一次,生成的Configuration对象也会存在应用的整个生命周期中。接下来就是parseConfiguration方法:
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
这个方法中有十几个子方法,对应这config文件里面所有一级标签。这里我们只关注一下mappers一级标签,因为上面我们提到,解析配置文件主要解析两种文件,一种是全局配置文件,第二种就是Mapper.xml,进入mapperElement()方法:
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
在这个方法中会新建一个XMLMapperBuilder 对象,并调用parser方法开始解析mapper.xml,里面有两个方法:
configurationElement()—— 解 析 所 有 的 子 标 签 , 其 中buildStatementFromContext()最终获得MappedStatement对象。
bindMapperForNamespace()——把namespace(接口类型)和工厂类绑定起来。
无论是按 package 扫描,还是按接口扫描,最后都会调用到 MapperRegistry 的addMapper()方法。
MapperRegistry里面维护的其实是一个Map容器,存储接口和代理工厂的映射关系。
public void addMapper(Class type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
除了映射器文件,在这里也会去解析Mapper接口方法上的注解。在addMapper()方法里面创建了一个MapperAnnotationBuilder,我们点进去看一下parse()方法。parseCache() 和 parseCacheRef() 方 法 其 实 是 对 @CacheNamespace 和@CacheNamespaceRef这两个注解的处理。parseStatement()方法里面的各种 getAnnotation(),都是对注解的解析,比如@Options,@SelectKey,@ResultMap等等。最后同样会解析成 MappedStatement 对象,也就是说在 XML 中配置,和使用注解配置,最后起到一样的效果。
配置文件解析完成后会调用build方法返回一个DefaultSqlSessionFactory对象:
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
到这里,配置文件解析完成了。
二、会话创建
我们跟数据库的每一次连接,都需要创建一个会话,我们使用openSession()方法创建。
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
这个会话里面,需要包含一个Executor用来执行 SQL。Executor 又要指定事务类型和执行器的类型。所以我们会先从Configuration里面拿到Enviroment,Enviroment里面就有事务工厂。
- 创建Transaction
如果配置的是JDBC,则会使用Connection对象的commit()、rollback()、close()管理事务。
如果配置成MANAGED,会把事务交给容器来管理,比如JBOSS,Weblogic。因为我们跑的是本地程序,如果配置成MANAGE不会有任何事务。如 果 是 Spring + MyBatis , 则 没 有 必 要 配 置 , 因 为 我 们 会 直 接 在applicationContext.xml里面配置数据源和事务管理器,覆盖MyBatis的配置。 - 创建Executor
我们知道, Executor的基本类型有三种: SIMPLE、BATCH、REUSE,默认是SIMPLE (settingsElement()读取默认值),他们都继承了抽象类BaseExecutor。
问题:三种类型的区别(通过update()方法对比)?
SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
ReuseExecutor:执行 update 或 select,以 sql 作为 key查找 Statement 对象,存在就使用,不存在就创建,用完后,不关闭 Statement 对象,而是放置于 Map 内,供下一次使用。简言之,就是重复使用Statement对象。
BatchExecutor:执行 update(没有select,JDBC 批处理不支持 select),将所有 sql 都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement 对象,每个Statement 对象都是 addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。
如果在全局配置文件中配置了 cacheEnabled=ture,会用装饰器模式对 executor 进行包装:new CachingExecutor(executor)。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
包装完毕后,会执行:
executor = (Executor) interceptorChain.pluginAll(executor);
此处对executor进行包装,最终返回DefaultSqlSession,属性包括Configuration、Executor对象。
三、获得Mapper对象
现在我们已经有一个DefaultSqlSession了,必须找到Mapper.xml里面定义的statement ID,才能执行对应的SQL语句。
找到Statement ID有两种方式,一种是直接调用session的方法,在参数里面传入statement ID,这种方式属于硬编码,我们没有办法知道有多少处调用,修改起来也很麻烦。另外一个问题是如果参数传入错误,在编译阶段也不会报错,不利于预先发现问题:
Blog blog = (Blog) session.selectOne("com.gupaoedu.mapper.BlogMapper.selectBlogById ", 1);
所以在MyBatis后期版本提供了第二种方式,就是定义一个接口,然后再调用Mapper接口的方法。
由于我们的接口名称跟Mapper.xml的namespace是对应的,接口的方法跟Statement ID也都是对应的,所以根据方法就能找到对应的要执行的SQL。
BlogMapper mapper = session.getMapper(BlogMapper.class);
在这里我们要主要研究一下Mapper对象是怎么获得的,它的本质是什么?
DefaultSqlSession的getMapper()方法调用了Configuration的getMapper()方法。
public T getMapper(Class type) {
return configuration.getMapper(type, this);
}
Configuration的getMapper()方法,又调用了MapRegistry的getMapper()方法
public T getMapper(Class type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
我们知道,在解析mapper标签和Mapper.xml的时候已经把接口类型和类型对应的MapperProxyFactory放到一个map中,获取Mapper的代理对象,实际上是从map中获取对应的工厂类后,最终通过JDK动态代理创建的。
public T getMapper(Class type, SqlSession sqlSession) {
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
protected T newInstance(MapperProxy mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
思考:JDK的动态代理和MyBatis中用到的JDK动态代理有什么区别?
JDK动态代理:
JDK动态代理代理,在实现了InvocationHandler的代理类里面,需要传入一个被代理对象的实现类。
MyBatis中的JDK动态代理:
不需要实现类的原因:我们只需要根据接口类型+方法的名称,就可以找到StatementID了,而唯一要做的一件事情也是这件,所以不需要实现类。 在MapperProxy里面直接执行逻辑(也就是执行SQL)就可以
总结:获得Mapper对象的过程,实质上是获取了一个MapperProxy的代理对象,MapperProxy中有SqlSession、mapperInterface、methodCache。
四、执行SQL
Blog blog = mapper.selectBlogById(1);
由于所有的Mapper都是MapperProxy的代理对象,所以任意的方法都是执行MapperProxy的invoke()方法。
问题1:我们引入MapperProxy为了解决什么问题?硬编码和编译时检查的问题,它需要做的事情时根据方法找到对应的statement ID。
问题2:这里没有实现类,进入到Invoke方法的时候做了什么事情?他是怎么找到我们要执行的SQL的?
我们看一下MapperProxy的invoke方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
- 首先判断是否要执行SQL还是直接执行方法
Object本身的方法和Java 8中的默认方法不需要取执行SQL - 获取缓存
这里加入缓存时为了提升MapperMethod的获取速度
// 获取缓存,保存了方法签名和接口方法的关系 final MapperMethod mapperMethod = cachedMapperMethod(method);
Map 的 computeIfAbsent()方法:只有 key 不存在或者 value 为 null 的时候才调用mappingFunction()。
接下来又调用了mapperMethod.execute()方法,MapperMethod里面主要有两个属性,一个时SqlCommand,一个时MethodSignature,这两个都是MapperMethod的内部类。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
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 {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
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;
}
我们以查询SQL为例,会走到sqlSession.selectOne()方法,selectOne()最终也是调用了selectList()。
public T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
在SelectList()中,我们先根据commandname(StatementID)从Configuration中拿到MappedStatement,这个ms上面有我们在xml中配置的所有属性,包括id、statementType、sqlSource、useCache、入参、出参等等。
public List selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
然后执行executor.query(),前面我们说到了Executor有三种基本类型,SIMPLE/REUSE/BATCH,还有一种包装类型,CachingExecutor。
那么在这里到底会选择哪一种执行器呢?我们要回过头去看看DefaultSqlSession 在初始化的时候是怎么赋值的,这个就是我们的会话创建过程。如果启用了二级缓存,就会先调用CachingExecutor 的query()方法,里面有缓存相关的操作,然后才是再调用基本类型的执行器,比如默认的SimpleExecutor。在没有开启二级缓存的情况下,先会走到BaseExecutor的query()方法(否则会先
走到CachingExecutor)。
BaseExecutor.query()方法:
public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List list;
try {
queryStack++;
list = resultHandler == null ? (List) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
- 创建cacheKey:从Configuration 中获取MappedStatement, 然后从BoundSql 中获取SQL 信
息,创建CacheKey。这个CacheKey就是缓存的Key。然后再调用另一个query()方法。
2)在query方法中,queryStack用于记录查询栈,防止递归查询重复处理缓存。flushCache=true的时候,会先清理本地缓存(一级缓存):clearLocalCache(); 如果没有缓存,会从数据库查询:queryFromDatabase(),如果LocalCacheScope == STATEMENT,会清理本地缓存。
从数据库查询数据:
private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
a)缓存
先在缓存用占位符占位。执行查询后,移除占位符,放入数据。
b)查询
执行Executor的doQuery();默认是SimpleExecutor
SimpleExecutor.doQuery():
public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
1)创建StatementHandler
在configuration.newStatementHandler()中,new 一个StatementHandler,先得到RoutingStatementHandler。RoutingStatementHandler 里 面 没 有 任 何 的 实 现 , 是 用 来 创 建 基 本 的StatementHandler 的。这里会根据 MappedStatement 里面的 statementType 决定
StatementHandler 的 类 型 。 默 认 是 PREPARED ( STATEMENT 、 PREPARED 、
CALLABLE)。
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
StatementHandler 里面包含了处理参数的 ParameterHandler 和处理结果集的ResultSetHandler。
这两个对象都是在上面new的时候创建的。
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//省略部分代码
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
这三个对象都是可以被插件拦截的四大对象之一,所以在创建之后都要用拦截器进行包装的方法。
2)创建Statement
用new 出来的StatementHandler创建Statement对象——prepareStatement()方法对语句进行预编译,处理参数。handler.parameterize(stmt) ;
3)执行的StatementHandler的query()方法
RoutingStatementHandler的query()方法。delegate 委派,最终执行PreparedStatementHandler的query()方法
4)执行PreparedStatement的execute()方法
后面就是JDBC包中的PreparedStatement的执行了。
5)ResultSetHandler处理结果集
return resultSetHandler.handleResultSets(ps);