MyBatis的执行流程看源码的话相对来说还是比较简单的,只需要跟着源码走,用点心,就应该OK,这里按代码执行逻辑大概走一遍执行流程。
一、环境搭建
可以参考一、快速搭建MyBatis开发环境(配置版+注解版)来搭建环境
二、源码分析
看源码我们不能一头雾水的跟着工具调试下去,得带着目的走,我们得找到看源码的目的,我的目的是了解框架的执行逻辑,是怎么做到的?比如MyBatis的运行,先看下MyBatis是怎么运行的。
1、MyBatis的使用
首先,我们引入依赖
pom.xml
<!-- mybatis框架依赖 --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.4.6</version></dependency><!-- mysql数据库链接依赖 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.45</version></dependency>
然后,配置文件配置数据库连接信息
SqlMapConfig.xml
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><!-- mybatis的主配置文件 --><configuration><!-- 配置环境 --><environments default="mysql"><!-- 配置mysql的环境--><environment id="mysql"><!-- 配置事务的类型--><transactionManager type="JDBC"></transactionManager><!-- 配置数据源(连接池) --><dataSource type="POOLED"><!-- 配置连接数据库的4个基本信息 --><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://******:3309/suibibk"/><property name="username" value="root"/><property name="password" value="*******"/></dataSource></environment></environments><mappers><!-- 不一定非得叫UserMapper.xml --><mapper resource="mybatis/UserMapper.xml"/></mappers></configuration>
然后我们写一个Mapper
UserMapper.java
public interface UserMapper {public List<User> findAllUser();}
再写一个SQL配置文件
UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.suibibk.mybatis.UserMapper"><!--配置查询所有--><select id="findAllUser" resultType="com.suibibk.mybatis.User">select * from user</select></mapper>
然后就可以执行了
public class Test {public static void main(String[] args) throws IOException {//1、读取配置文件InputStream in = Resources.getResourceAsStream("mybatis/SqlMapConfig.xml");//2、创建SqlSessionFactory工厂SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();SqlSessionFactory factory = builder.build(in);//3、使用工厂生产SqlSession对象SqlSession sqlSession = factory.openSession();//4、使用SqlSession创建Dao接口的代理对象UserMapper userMapper = sqlSession.getMapper(UserMapper.class);//5、使用代理对象执行方法List<User> user = userMapper.findAllUser();System.out.println("user:"+user.toString());//6、释放资源sqlSession.close();in.close();}}
这个可比用原始的JDBC方便了,我们只需要在UserMapper.xml文件加上要执行的sql脚本和UserMapper.java上加上执行方法就可以了。
所以我好奇的问题是,MyBatis是怎么做到的呢?为啥UserMapper.java和UserMapper.xml可以这样关联?什么时候连接数据库?什么时候去执行sql?
带着这些疑问,我们来跟踪下源码!
2、啥时候解析配置文件?
很多框架第一步就是初始化配置文件,然后把配置文件相关的信息加载到一个配置类里,我们来看看MyBatis是不是也这样做的。
先看第一步读取配置文件
//1、读取配置文件InputStream in = Resources.getResourceAsStream("mybatis/SqlMapConfig.xml");
很明显,就是把我们的配置文件读成以恶搞输入流,这个没啥好看的。
第二步骤创建SessionFactory工厂
//2、创建SqlSessionFactory工厂SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();SqlSessionFactory factory = builder.build(in);
很明显就是根据第一步的输入流中的配置文件构造对象,我们跟进build方法去,我这只摘录主要方法
SqlSessionFactoryBuilder.java
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.}}}
上面开始是生成文件解析器,这个可以不用看,主要是看用文件解析器解析文件的逻辑即可。
parser.parse()
XMLConfigBuilder.java
public Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;parseConfiguration(parser.evalNode("/configuration"));return configuration;}
很明显我们要看解析configuration节点(SqlMapConfig.xml)。
继续走
private void parseConfiguration(XNode root) {try {//issue #117 read properties firstpropertiesElement(root.evalNode("properties"));Properties settings = settingsAsProperties(root.evalNode("settings"));loadCustomVfs(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 #631environmentsElement(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);}}
我们的SqlMapConfig.xml中,主要的是environments也就是配置数据库相关链接的节点和mappers配置sql脚本的节点。
这里先看怎么解析environments。
private void environmentsElement(XNode context) throws Exception {if (context != null) {if (environment == null) {environment = context.getStringAttribute("default");}for (XNode child : context.getChildren()) {String id = child.getStringAttribute("id");if (isSpecifiedEnvironment(id)) {TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));DataSource dataSource = dsFactory.getDataSource();Environment.Builder environmentBuilder = new Environment.Builder(id).transactionFactory(txFactory).dataSource(dataSource);configuration.setEnvironment(environmentBuilder.build());}}}}
我们可以看到生成了一个DataSource,然后构造成一个环境变量放到了configuration中。

这,果然框架都万变不离其中,Spring也是,最终都是把配置文件解析的内容放到配置类里,然后给后面用。我们先略微看下configuration
public class Configuration {protected Environment environment;protected boolean safeRowBoundsEnabled;...protected final MapperRegistry mapperRegistry = new MapperRegistry(this);protected final InterceptorChain interceptorChain = new InterceptorChain();protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");...}
看到没,配置类超级多内容,但是可以肯定的是,解析的配置文件肯定放这里的。
上面把数据库链接的相关信息都放到了Configuration.environment中,我们再来看看看mappers配置放到哪里?
跟踪mappers节点的解析方法
mapperElement(root.evalNode("mappers"));
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.");}}}}}
这里分了几个类,我们的配置文件中
<mapper resource="mybatis/UserMapper.xml"/>
用的是resource,所以直接看代码
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();}
public void parse() {if (!configuration.isResourceLoaded(resource)) {configurationElement(parser.evalNode("/mapper"));configuration.addLoadedResource(resource);bindMapperForNamespace();}parsePendingResultMaps();parsePendingCacheRefs();parsePendingStatements();}
这里我们知道Resource配置的是mybatis/UserMapper.xml路径,肯定是存在的,所以走
configurationElement(parser.evalNode("/mapper"));
private void configurationElement(XNode context) {try {String namespace = context.getStringAttribute("namespace");if (namespace == null || namespace.equals("")) {throw new BuilderException("Mapper's namespace cannot be empty");}builderAssistant.setCurrentNamespace(namespace);cacheRefElement(context.evalNode("cache-ref"));cacheElement(context.evalNode("cache"));parameterMapElement(context.evalNodes("/mapper/parameterMap"));resultMapElements(context.evalNodes("/mapper/resultMap"));sqlElement(context.evalNodes("/mapper/sql"));buildStatementFromContext(context.evalNodes("select|insert|update|delete"));} catch (Exception e) {throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);}}
这里我的配置文件中的是
<select id="findAllUser" resultType="com.suibibk.mybatis.User">select * from user</select>
所以我们看
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
private void buildStatementFromContext(List<XNode> list) {if (configuration.getDatabaseId() != null) {buildStatementFromContext(list, configuration.getDatabaseId());}buildStatementFromContext(list, null);}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {for (XNode context : list) {final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);try {statementParser.parseStatementNode();} catch (IncompleteElementException e) {configuration.addIncompleteStatement(statementParser);}}}
继续
XMLStatementBuilder.java
public void parseStatementNode() {String id = context.getStringAttribute("id");String databaseId = context.getStringAttribute("databaseId");if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}Integer fetchSize = context.getIntAttribute("fetchSize");Integer timeout = context.getIntAttribute("timeout");String parameterMap = context.getStringAttribute("parameterMap");String parameterType = context.getStringAttribute("parameterType");Class<?> parameterTypeClass = resolveClass(parameterType);String resultMap = context.getStringAttribute("resultMap");String resultType = context.getStringAttribute("resultType");String lang = context.getStringAttribute("lang");LanguageDriver langDriver = getLanguageDriver(lang);Class<?> resultTypeClass = resolveClass(resultType);String resultSetType = context.getStringAttribute("resultSetType");StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);String nodeName = context.getNode().getNodeName();SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));boolean isSelect = sqlCommandType == SqlCommandType.SELECT;boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);boolean useCache = context.getBooleanAttribute("useCache", isSelect);boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);// Include Fragments before parsingXMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);includeParser.applyIncludes(context.getNode());// Parse selectKey after includes and remove them.processSelectKeyNodes(id, parameterTypeClass, langDriver);// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);String resultSets = context.getStringAttribute("resultSets");String keyProperty = context.getStringAttribute("keyProperty");String keyColumn = context.getStringAttribute("keyColumn");KeyGenerator keyGenerator;String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);if (configuration.hasKeyGenerator(keyStatementId)) {keyGenerator = configuration.getKeyGenerator(keyStatementId);} else {keyGenerator = context.getBooleanAttribute("useGeneratedKeys",configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;}builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered,keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);}
这里我们可以看到,全是解析select节点的,比如获取id
String id = context.getStringAttribute("id");
参数,返回类型
String parameterType = context.getStringAttribute("parameterType");Class<?> parameterTypeClass = resolveClass(parameterType);String resultMap = context.getStringAttribute("resultMap");String resultType = context.getStringAttribute("resultType");
解析sql
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
总之,就是把select节点彻底解析了。
解析到最后
public MappedStatement addMappedStatement(String id,SqlSource sqlSource,StatementType statementType,SqlCommandType sqlCommandType,Integer fetchSize,Integer timeout,String parameterMap,Class<?> parameterType,String resultMap,Class<?> resultType,ResultSetType resultSetType,boolean flushCache,boolean useCache,boolean resultOrdered,KeyGenerator keyGenerator,String keyProperty,String keyColumn,String databaseId,LanguageDriver lang,String resultSets) {if (unresolvedCacheRef) {throw new IncompleteElementException("Cache-ref not yet resolved");}id = applyCurrentNamespace(id, false);boolean isSelect = sqlCommandType == SqlCommandType.SELECT;MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType).resource(resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired(valueOrDefault(flushCache, !isSelect)).useCache(valueOrDefault(useCache, isSelect)).cache(currentCache);ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);if (statementParameterMap != null) {statementBuilder.parameterMap(statementParameterMap);}MappedStatement statement = statementBuilder.build();configuration.addMappedStatement(statement);return statement;}
public void addMappedStatement(MappedStatement ms) {mappedStatements.put(ms.getId(), ms);}
终于跟预料之中,把解析好的对象封装到了configuration的mappedStatements中

2、啥时候连接数据库?
既然配置都已经解析了,那什么时候链接数据库呢,我们看如下代码
//3、使用工厂生产SqlSession对象SqlSession sqlSession = factory.openSession();//4、使用SqlSession创建Dao接口的代理对象UserMapper userMapper = sqlSession.getMapper(UserMapper.class);//5、使用代理对象执行方法List<User> user = userMapper.findAllUser();
首先是获取SqlSession对象,我们是通过SqlSession对象来获取UserMapper的,我们跟进去这里有没有连数据库?
@Overridepublic SqlSession openSession() {return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}
这里选的是DefaultSqlSessionFactory.java的方法,为什么呢?因为在上一步生成SqlSessionFactory对象的时候
public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);}
new的就是这个对象。
继续跟进去
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();}}
上面就创建了Transaction和Executor,然后创建DefaultSqlSession对象返回,并没有进行数据库的链接,不过这个是当然的,数据库链接肯定要在只sql的时候才会创建啊,所以我们直接看数据库操作的代码
//4、使用SqlSession创建Dao接口的代理对象UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
看到这里,立马就想到了肯定用了jdk的代理,生成的UserMapper肯定是代理的对象,我们跟踪进去看看
@Overridepublic <T> T getMapper(Class<T> type) {return configuration.<T>getMapper(type, this);}
这里是DefaultSqlSession.java里面的方法,因为上面创建的就是DefaultSqlSession对象,继续
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) 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);}}
是用MapperProxyFactory来创建代理对象的,所以创建的对象肯定是MapperProxy对象,方法的执行肯定也是调用MapperProxy的invoke方法,我们跟踪进去看看
public T newInstance(SqlSession sqlSession) {final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);}
protected T newInstance(MapperProxy<T> mapperProxy) {return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}
是了嘛,到这里就没必要继续下去了,动态代理。

然后我们直接看
//5、使用代理对象执行方法List<User> user = userMapper.findAllUser();
这行代码,这里肯定是调用MapperProxy的invok方法
@Overridepublic 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);}
return mapperMethod.execute(sqlSession, args);
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);}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;}
我们这里是查询多条语句,所以走
result = executeForMany(sqlSession, args);
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {List<E> result;Object param = method.convertArgsToSqlCommandParam(args);if (method.hasRowBounds()) {RowBounds rowBounds = method.extractRowBounds(args);result = sqlSession.<E>selectList(command.getName(), param, rowBounds);} else {result = sqlSession.<E>selectList(command.getName(), param);}// issue #510 Collections & arrays supportif (!method.getReturnType().isAssignableFrom(result.getClass())) {if (method.getReturnType().isArray()) {return convertToArray(result);} else {return convertToDeclaredCollection(sqlSession.getConfiguration(), result);}}return result;}
继续走
result = sqlSession.<E>selectList(command.getName(), param);
@Overridepublic <E> List<E> selectList(String statement, Object parameter) {return this.selectList(statement, parameter, RowBounds.DEFAULT);}
这里也是DefaultSqlSession.java里面的方法,继续
@Overridepublic <E> List<E> 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();}}
看,这里的MappedStatement,根据statement去configuration中的mappedStatements获取的,这个是之前解析mappers的时候放进去的,获取后继续。
@Overridepublic <E> List<E> 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);}
再继续
@Overridepublic <E> List<E> 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<E> list;try {queryStack++;list = resultHandler == null ? (List<E>) 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 #601deferredLoads.clear();if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {// issue #482clearLocalCache();}}return list;}
我们很容易一眼就看到查询数据库的地方
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
继续
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {List<E> 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;}
@Overridepublic <E> List<E> 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.<E>query(stmt, resultHandler);} finally {closeStatement(stmt);}}
这里SimpleExecutor.java,之前返回DefaultSqlSession的时候指定的。终于,历经坎坷,看到了熟悉的Statement。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;Connection connection = getConnection(statementLog);stmt = handler.prepare(connection, transaction.getTimeout());handler.parameterize(stmt);return stmt;}
终于看到了数据库连接的地方。获取Statement后应该就可以去查数据了把,查看
return handler.<E>query(stmt, resultHandler);
@Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {String sql = boundSql.getSql();statement.execute(sql);return resultSetHandler.<E>handleResultSets(statement);}
好了,主脉络清晰了,其它的边边角角传入参数,传出参数什么的各种配置属性,以后再慢慢看了!
