第一步:读取配置文件
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
将配置文件读成字节数据流,并没有进行解析。
第二步:进行配置文件解析
解析配置文件并封装为一个配置类对象,基于配置类对象并创建一个SqlSessionFactory
对象。
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
2.1
进一步去源码观察解析代码
//它使用了一个参照了XML文档或更特定的SqlMapConfig.xml文件的Reader实例。
//可选的参数是environment和properties。Environment决定加载哪种环境(开发环境/生产环境),包括数据源和事务管理器。
//如果使用properties,那么就会加载那些properties(属性配置文件),那些属性可以用${propName}语法形式多次用在配置文件中。和Spring很像,一个思想?
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
//委托XMLConfigBuilder来解析xml文件,并构建
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
//parser真正的去执行XMl文件解析。
return build(parser.parse());
} catch (Exception e) {
//这里是捕获异常,包装成自己的异常并抛出的idiom?,最后还要reset ErrorContext
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
2.1.1
我们进入到parser.parse()
看一下解析代码。
//解析配置
public Configuration parse() {
//如果已经解析过了,报错,如果没解析过parsed默认为false.
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
//开始解析之后进行修改。
parsed = true;
//根节点是configuration
//parser是XPathPaeser解析器对象,读取根节点内的数据
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
2.1.2
我们先看一下parseConfiguration(XNode root)
方法:
private void parseConfiguration(XNode root) {
try {
//分步骤解析,对不同标签进行逐步解析。
//1.properties
propertiesElement(root.evalNode("properties"));
//2.类型别名
typeAliasesElement(root.evalNode("typeAliases"));
//3.插件
pluginElement(root.evalNode("plugins"));
//4.对象工厂
objectFactoryElement(root.evalNode("objectFactory"));
//5.对象包装工厂
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//6.设置
settingsElement(root.evalNode("settings"));
// read it after objectFactory and objectWrapperFactory issue #631
//7.环境
environmentsElement(root.evalNode("environments"));
//8.databaseIdProvider
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//9.类型处理器
typeHandlerElement(root.evalNode("typeHandlers"));
//10.映射器
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
2.1.3
通过以上代码结合注释可知,不同的标签采用不同的方法进行解析,我们就以ropertiesElement(XNode context)
为例进行一下追踪。
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
//如果在这些地方,属性多于一个的话,MyBatis 按照如下的顺序加载它们:
//1.在 properties 元素体内指定的属性首先被读取。
//2.从类路径下资源或 properties 元素的 url 属性中加载的属性第二被读取,它会覆盖已经存在的完全一样的属性。
//3.作为方法参数传递的属性最后被读取, 它也会覆盖任一已经存在的完全一样的属性,这些属性可能是从 properties 元素体内和资源/url 属性中加载的。
//传入方式是调用构造函数时传入,public XMLConfigBuilder(Reader reader, String environment, Properties props)
//1.XNode.getChildrenAsProperties函数方便得到孩子所有Properties
Properties defaults = context.getChildrenAsProperties();
//2.然后查找resource或者url,加入前面的Properties
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
//3.Variables也全部加入Properties
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
//解析完成之后进行封装,封装到Mybatis的核心配置类当中。
configuration.setVariables(defaults);
}
}
2.1.4
我们看完了ropertiesElement(XNode context)
,还有关键的一点,就是2.1.3中的mapperElement(XNode parent)
,它是对内部的映射文件进行解析。经过以上解析我们就完成了对Configuration
对象的封装,我们来看一下这个类。
public class Configuration {
//环境
protected Environment environment;
//---------以下都是<settings>节点-------
protected boolean safeRowBoundsEnabled = false;
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase = false;
protected boolean aggressiveLazyLoading = true;
protected boolean multipleResultSetsEnabled = true;
protected boolean useGeneratedKeys = false;
protected boolean useColumnLabel = true;
//默认启用缓存
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls = false;
protected String logPrefix;
protected Class <? extends Log> logImpl;
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
protected Integer defaultStatementTimeout;
//默认为简单执行器
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
//---------以上都是<settings>节点-------
protected Properties variables = new Properties();
//对象工厂和对象包装器工厂
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
//映射注册机
protected MapperRegistry mapperRegistry = new MapperRegistry(this);
//默认禁用延迟加载
protected boolean lazyLoadingEnabled = false;
protected ProxyFactory proxyFactory = new JavassistProxyFactory();
protected String databaseId;
protected Class<?> configurationFactory;
protected final InterceptorChain interceptorChain = new InterceptorChain();
//类型处理器注册机
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
//类型别名注册机
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
//映射的语句,存在Map里,这里面存放着解析完成的映射文件。
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
//缓存,存在Map里
protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
//结果映射,存在Map里
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");
protected final Set<String> loadedResources = new HashSet<String>();
protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
//不完整的SQL语句
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();
protected final Map<String, String> cacheRefMap = new HashMap<String, String>();
public Configuration(Environment environment) {
this();
this.environment = environment;
}
public Configuration() {
//注册更多的类型别名,至于为何不直接在TypeAliasRegistry里注册,还需进一步研究
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
...
}
2.2
经过以上分析,我们最终完成了 SqlSessionFactory build(Configuration config)
参数的封装。
2.2.1
最后一个build方法使用了一个Configuration作为参数,并返回DefaultSqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
DefaultSqlSessionFactory
就是SqlSessionFactory
的实现类,此处使用了构建者设计模式。
第三步:接口代理类的生成流程
SqlSession: SqlSession是一个接口,它有两个实现类DefaultSqlSession和SqlSessionManager(已经弃用)。SqlSession是MyBatis中用于和数据库交互的顶层类,通常将它与ThreadLocal绑定,一个会话使用一个SqlSession,使用完之后进行关闭。
public class DefaultSqlSession implements SqlSession {
//配置类
private Configuration configuration;
//执行器
private Executor executor;
}
Executor:它是一个执行器,为接口。有三个常用的实现类:BatchExecutor、ReuseExecutor、SimpleExecutor(默认)
介绍完预备知识,进行源码分析。
3.1
从sqlSessionFactory.openSession();
进行追踪
3.1.1
public SqlSession openSession() {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}
第一个参数为执行器类型,默认为:ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
,第二个参数为:事务隔离级别,第三个参数为是否自动提交。
3.1.2
来看一下构造方法的具体执行逻辑:
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);
//然后产生一个DefaultSqlSession
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();
}
}
3.2
开始执行对应的xml文件内容。交给执行器执行。这里有两种方式,
sqlSession.selectList("namespace.id");
UserDao userDao = sqlSession.getMapper(UserDao.class);
平时工作中第二种用的比较多,我们便以第二种为例子,展开讨论。
3.2.1
我们讨论的这种方式,大致意思就是:通过接口类配合注解(或xml文件)生成一个代理对象。
我们回到解析XML文件代码中进行一下回顾:
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//从上面代码往下追,追到下面代码:
private void parseConfiguration(XNode root) {
try {
//分步骤解析,对不同标签进行逐步解析。
//1.properties
propertiesElement(root.evalNode("properties"));
//2.类型别名
typeAliasesElement(root.evalNode("typeAliases"));
//3.插件
pluginElement(root.evalNode("plugins"));
//4.对象工厂
objectFactoryElement(root.evalNode("objectFactory"));
//5.对象包装工厂
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//6.设置
settingsElement(root.evalNode("settings"));
// read it after objectFactory and objectWrapperFactory issue #631
//7.环境
environmentsElement(root.evalNode("environments"));
//8.databaseIdProvider
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//9.类型处理器
typeHandlerElement(root.evalNode("typeHandlers"));
//10.映射器
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
我们看一下第10步、这步对我们很关键,我们配置的SQL的XML文件就可能在这里面,在MyBatis配置文件中配置的Mapper路径。
我们点进去看一下:
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//判断你的Mapper里面是 package还是其他
if ("package".equals(child.getName())) {
//如果是package标签并且有name属性,name属性就是XML包的路径。
String mapperPackage = child.getStringAttribute("name");
//将这个包名传递到configuration配置类当中去
//它会将包下的所有接口注册到mapperRegistry[1]中去。
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) {
//10.1使用类路径
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
//映射器比较复杂,调用XMLMapperBuilder
//注意在for循环里每个mapper都重新new一个XMLMapperBuilder,来解析
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
//10.2使用绝对url路径
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
//映射器比较复杂,调用XMLMapperBuilder
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
//10.3使用java类名
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.");
}
}
}
}
}
[1] MapperRegistry
这个对象就是Mapper注册表对象。
public class MapperRegistry {
private Configuration config;
//将已经添加的映射都放入HashMap,一个接口对应着一个接口代理工厂。
//key :接口类型
//value : 接口代理工厂
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
}
3.2.2
我们可以开始对UserDao userDao = sqlSession.getMapper(UserDao.class);
进行一探究竟了。我们进入到defaultSession
中。
@Override
public <T> T getMapper(Class<T> type) {
//最后会去调用MapperRegistry.getMapper
return configuration.<T>getMapper(type, this);
}
我们继续到configuration.<T>getMapper(type, this);
中看一看:
1、
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
2、
//返回代理类
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//根据我们的key值从hashMap获取出它的代理工厂类
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);
}
}
3、
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
//用JDK自带的动态代理生成映射器
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
第四步:代理类的执行过程
4.1
经过前面的代码阅读,我们知道了通过JDK动态代理生成了接口代理类,我们去看看具体执行逻辑。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理以后,所有Mapper的方法调用时,都会调用这个invoke方法
//并不是任何一个方法都需要执行调用代理对象进行执行,如果这个方法是Object中通用的方法(toString、hashCode等)无需执行
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
//这里优化了,去缓存中找MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
//执行
return mapperMethod.execute(sqlSession, args);
}
这段代码是不是很熟悉,Mybatis的插件也是在这块进行的加强。不过我们这次不看上面,看mapperMethod.execute(sqlSession, args);
//执行
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//可以看到执行时就是4种情况,insert|update|delete|select,分别调用SqlSession的4大类方法
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
if (method.returnsVoid() && method.hasResultHandler()) {
//如果有结果处理器
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
//如果结果有多条记录
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
//如果结果是map
result = executeForMap(sqlSession, args);
} else {
//否则就是一条记录
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
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;
}
4.1.1
根据第三步的SqlSession的默认实现进行分析:其实也类似于第一种情况了:sqlSession.selectList("namespace.id");
我们来到DefaultSqlSession
:
//核心selectList
//第一个参数1、对应的statementid。2、参数。3、分页
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//根据statement id找到对应的MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
//转而用执行器来查询结果,注意这里传入的ResultHandler是null
//wrapCollection(parameter)把参数封装为集合。
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();
}
}
4.1.2
//我们来到了executor进行执行SQL。
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//得到绑定sql
BoundSql boundSql = ms.getBoundSql(parameter);
//创建缓存Key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
//查询
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
4.1.3
public <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());
//如果Executor已经关闭,报错
if (closed) {
throw new ExecutorException("Executor was closed.");
}
//先清局部缓存,再查询.但仅查询堆栈为0,才清。为了处理递归调用
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
//加一,这样递归调用到上面的时候就不会再清局部缓存了
queryStack++;
//先根据cachekey从localCache去查
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
//若查到localCache缓存,处理localOutputParameterCache
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();
}
//清空延迟加载队列
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
//如果是STATEMENT,清本地缓存
clearLocalCache();
}
}
return list;
}
4.1.4
我们来到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);
//如果是存储过程,OUT参数也加入缓存
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
4.1.5
继续追list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
//select-->结果给ResultHandler
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
//先执行Statement.execute,然后交给ResultSetHandler.handleResultSets进行结果集的封装。
return resultSetHandler.<E>handleResultSets(statement);
}
总结一句话吧SqlSession开启会话之后,调用executor进行SQL执行,executor又去调用不同的Handler去执行SQL执行的不同阶段
源码贴的有点多,语言解释有点少,解决缺点,早日写出优质博客。后期会逐步修改