本博文只为学习记录方便后期回顾,大家可直接查看参考链接学习,文中如有错误,欢迎指正!
参考链接:
SpringBoot中,Mybatis的执行流程源码解析
springboot-mybatis 源码剖析
SpringBoot与Mybatis整合源码深度解析
从根上理解:Mybatis中数据库的列和Java字段是怎么映射的?
准备阶段
我们将SpringBoot项目启动起来的时候,它会为我们加载一些组件以实现MyBatis的执行
- SqlSessionFactoryBuilder:作用就是创建一个构建器,一旦创建了SqlSessionFactory,它的任务就算完成了,可以回收,使用建造者模式
- SqlSessionFactory:SqlSession的工厂,用于创建SqlSession,SqlSession相当于JDBC的一个Connection对象,每次应用程序需要访问数据库,我们就要通过SqlSessionFactory创建一个SqlSession,所以SqlSessionFactory在整Mybatis整个生命周期中(每个数据库对应一个SqlSessionFactory,是单例产生的)。
- SqlSession:Through this interface you can execute commands, get mappers and manage transactions,官方注释如上,通过它可以执行命令,接口中有selectOne,selectList等方法;获取mapper和管理事务,初始化时会生成SqlSessionTelmplate。
- MapperRegistry:主要调用它的getMapper方法,获取需要的mapper,其中有一个Map<Class<?>, MapperProxyFactory<?>> - knownMappers字段,通过这个map获取我们需要的MapperProxyFactory,从而创建MapperProxy对象。
- MapperProxy:其中封装了三个字段,sqlSession、mapperInterface和methodCache,mapperInterface是我们在dao层写的接口
1.启动类注解
@SpringBootApplication
@MapperScan(value = "com.example.demo.dao")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
2.@MapperScan注解
@MapperScan注解,会使用@Import+ImportBeanDefinitionRegistrar接口的方法向容器中注册一个bean,
3.注册bean和生成beanDefinition,添加到ioc容器中
这一步不细述,直接看这篇:SpringBoot与Mybatis整合源码深度解析
4. MybatisAutoConfiguration
5.构造会话工厂 SqlSessionFactory
创建SqlSessionFactoryBean
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
this.applyConfiguration(factory);
// 省略代码 设置SqlSessionFactoryBean 属性
Resource[] mapperLocations = this.properties.resolveMapperLocations();
if (!ObjectUtils.isEmpty(mapperLocations)) {
factory.setMapperLocations(mapperLocations);
}
Set<String> factoryPropertyNames = (Set)Stream.of((new BeanWrapperImpl(SqlSessionFactoryBean.class)).getPropertyDescriptors()).map(FeatureDescriptor::getName).collect(Collectors.toSet());
Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
factory.setScriptingLanguageDrivers(this.languageDrivers);
if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
defaultLanguageDriver = this.languageDrivers[0].getClass();
}
}
if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
}
this.applySqlSessionFactoryBeanCustomizers(factory);
return factory.getObject();
}
applyConfiguration方法会为SqlSessionFactoryBean 加载配置
private void applyConfiguration(SqlSessionFactoryBean factory) {
org.apache.ibatis.session.Configuration configuration = this.properties.getConfiguration();
if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
configuration = new org.apache.ibatis.session.Configuration();
}
if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
Iterator var3 = this.configurationCustomizers.iterator();
while(var3.hasNext()) {
ConfigurationCustomizer customizer = (ConfigurationCustomizer)var3.next();
customizer.customize(configuration);
}
}
factory.setConfiguration(configuration);
}
通过SqlSessionFactoryBean下的getObject获取SqlSessionFactory
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
this.afterPropertiesSet();
}
return this.sqlSessionFactory;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.dataSource, "Property 'dataSource' is required");
Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together");
this.sqlSessionFactory = this.buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
//省略部分代码
//1.解析mapper文件,很重要的
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
} else {
//2.遍历所有的mapper文件
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
//3.解析mapper xml文件
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
}
//4.使用targetConfiguration构建DefaultSqlSessionFactory
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
6.XMLMapperBuilder类的parse方法
public void parse() {
//1.判断是否已经解析过
if (!configuration.isResourceLoaded(resource)) {
//2.解析mapper文件,从mapper节点开始
configurationElement(parser.evalNode("/mapper"));
//3.将resource添加到已加载列表
configuration.addLoadedResource(resource);
//4.绑定namespace的mapper
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
第2步看7
第4步看11
7.XMLMapperBuilder类的configurationElement方法
private void configurationElement(XNode context) {
try {
// 1.获取namespace属性
String namespace = context.getStringAttribute("namespace");
if (namespace != null && !namespace.isEmpty()) {
// 2.设置currentNamespace属性
this.builderAssistant.setCurrentNamespace(namespace);
// 3.解析parameterMap、resultMap、sql等节点
this.cacheRefElement(context.evalNode("cache-ref"));
this.cacheElement(context.evalNode("cache"));
// 这两布中获奖mybatis中的字段和类对应上
this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
this.resultMapElements(context.evalNodes("/mapper/resultMap"));
this.sqlElement(context.evalNodes("/mapper/sql"));
this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} else {
throw new BuilderException("Mapper's namespace cannot be empty");
}
} catch (Exception var3) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + this.resource + "'. Cause: " + var3, var3);
}
}
8. resultMapElements中会解析xml中的resultMap
这部分对应标题,mybatis如何匹配java中的类
将xml中的resultMap注册成resultMappings,最后**resultMapResolver.resolve();**会把生成的resultMap封装到Configuration的resultMaps中。
9.buildStatementFromContext(context.evalNodes(“select|insert|update|delete”));
private void buildStatementFromContext(List<XNode> list) {
if (this.configuration.getDatabaseId() != null) {
this.buildStatementFromContext(list, this.configuration.getDatabaseId());
}
// 解析增删改查节点,封装成Statement
this.buildStatementFromContext(list, (String)null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
Iterator var3 = list.iterator();
while(var3.hasNext()) {
XNode context = (XNode)var3.next();
// 1.构建XMLStatementBuilder
XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);
try {
// 2.解析节点
statementParser.parseStatementNode();
} catch (IncompleteElementException var7) {
this.configuration.addIncompleteStatement(statementParser);
}
}
}
解析Mapper文件,逐步解析Mapper文件中的子节点,关键是解析select|insert|update|delete节点,然后封装Statement对象,然后使用statementParser.parseStatementNode();进行解析select|insert|update|delete节点,具体代码看代码块14,
这边每个 XNode 都相当于如下的一个 SQL,下面封装的每个 MappedStatement 可以理解就是每个 SQL。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.dao.StudentMapper">
<resultMap id="studentMap" type="com.example.demo.domain.Student">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="course" column="course"/>
<result property="score" column="score"/>
<result property="deleted" column="deleted"/>
<result property="createTime" column="createTime"/>
</resultMap>
<select id="queryAll" resultType="com.example.demo.domain.Student" resultMap="studentMap">
select * from student;
</select>
</mapper>
10. XMLStatementBuilder类的parseStatementNode方法
public void parseStatementNode() {
//忽略解析过程,就是解析出select|insert|update|delete节点有的属性如id、parameterType、resultMap等属性
//1.添加MapperStatement
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
//MapperBuilderAssistant#addMappedStatement
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");
}
// 2.将id填充上namespace,例如:selectByPrimaryKey变成
// it.cast.wechat.mapper.AddressMapper.selectByPrimaryKey
id = applyCurrentNamespace(id, false);
//3.判断是否为select节点
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//4.使用参数构建MappedStatement.Builder
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);
}
//5.构建MappedStatement
MappedStatement statement = statementBuilder.build();
//6.添加到MappedStatement集合中(重要)
configuration.addMappedStatement(statement);
return statement;
}
//Configuration#addMappedStatement
public void addMappedStatement(MappedStatement ms) {
//7.添加到可以看到mappedStatements缓存中,可以看到是以id位key, MappedStatement为value进行存储
//而id是it.cast.wechat.mapper.AddressMapper.selectByPrimaryKey namespace + select|insert|update|delete节点的id
mappedStatements.put(ms.getId(), ms);
}
这三个方法所完成的目标为:
- 解析select|insert|update|delete节点,
- 构建出一个MappedStatement
- 然后以id为key,id例如"com.example.demo.dao.StudentMapper.queryAll",MappedStatement为value保存到mappedStatements集合中。
11. XMLMapperBuilder类的bindMapperForNamespace方法
private void bindMapperForNamespace() {
//1.获取当前解析的Namespace
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
//2.根据namespace去加载类,可以看出来,如果namespace不是全限定类名就会找不到,
//纳闷为啥不知道报错
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
configuration.addLoadedResource("namespace:" + namespace);
//3.如果不存在以该Class为key的mapper,则进行添加
configuration.addMapper(boundType);
}
}
}
}
//Configuration#addMapper
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
//MapperRegistry#addMapper
public <T> void addMapper(Class<T> type) {
//4.判断是否为接口,不为接口,则直接忽略了
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 将type和以该type为参数构建的MapperProxyFactory作为键值对,放到knownMappers缓存中去
knownMappers.put(type, new MapperProxyFactory<>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
主要是将刚刚解析过的 mapper 文件的 namespace 放到 knownMappers 缓存中,key 为 namespace 对应的 class,value 为 MapperProxyFactory。
到此也就完成了对Mybatis的解析,然后生成一个Configuration类,放到第五步this.sqlSessionFactoryBuilder.build(targetConfiguration);构建SqlSessionFactory。
12. 使用SqlSessionFactory创建SqlSessionTemplate
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
Assert.notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
//使用jdk动态代理创建sqlSessionProxy, 使用sqlSessionProxy.getObject()创建sqlSession
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionInterceptor());
}
有了SqlSession也就是sqlSessionTemplate,会调用他的getMapper方法,在这里我们可以看见需要在Configurantion中获取mapper,而且type就是我们在dao层创建的接口的Class对象
用到了11中knownMappers,存储的时mapper接口和对应的mapperProxyFactory
执行阶段
从这里进去
1.MapperProxy的Invoke方法
2.MapperProxy的Invoke方法,这里出现了MapperMethod
3. 之后根据sql语句类型继续向下执行,我们这里是SELECT,返回多条信息
4.调用的是sqlSessionTemplate的selectList方法
上图的sqlSession是sqlSessionTemplate,然后会进入SqlSessionInterceptor.invoke()方法中创建sqlSession。
创建sqlsession
SqlSessionTemplate 本身是 SqlSession 的一个代理类,线程安全,用来绑定线程和 SqlSession。每次获取一个 SqlSession,首先从 ThreadLocal 中获取当前线程的 SqlSession,如果获取不到,那就 new 一个,然后放到 ThreadLocal 中。当然代码走到这里只是创建好了 SqlSessionTemplate,创建 SqlSession 啥的要到执行 mapper 时候才会执行。
5. 接着继续debug进到defaultSqlSession
6.BoundSql就是我们要执行的sql语句
7.进到query里,cache为null,继续查询
8.BaseExecutor中的queryFromDatabase
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
List list;
try {
list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
this.localCache.removeObject(key);
}
this.localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
this.localOutputParameterCache.putObject(key, parameter);
}
return list;
}
下面这个doQuery方法是SimpleExector调用
这里我们测试这个sql
<select id="queryOne" resultType="com.example.demo.domain.Student">
select * from student where name = #{name}
</select>
9.在这里设置参数
10.先调用RoutingStatementHandler的parameterize
11.然后进入PreparedStatementHandler的parameterize方法
12.这里进入DefaultParameterHandler的setParameters方法
13.参数设置完后再回到这里
14.这里的handleResultSets会进行结果映射到java的类,这个很重要
这里rsw参数里存储了字段名称和类型
15.一直debug到后面,其中有一个applyPropertyMappings方法 比较重要,
这里它会进行一个循环,会对实体类进行赋值(其中ResultMap,指定的属性和列对应关系,在我们启动项目的时候,就在加载xml文件的时候,被创建好了,这个看8)
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#applyPropertyMappings
16.设置结束后想本地缓存添加,之后就是关闭资源,不断的向前返回result结果
这里再说下,本地缓存key的生成,cacheKey比较重要的2个方法:
- update:生成cacheKey,主要是往uplist中填充值;
- equals:比较2个key是否相等;判断cacheKey是否相等:主要是比较cacheKey的属性:hashcode ,checksum ,count ,updateList
一共调用了6次update方法。
id:全限定类名+方法名
offset:偏移量(默认:0)
limit:查询数据条数(默认:Integer.MAX_VALUE)
sql:查询语句
parameterValue:查询参数的值
environmentId:在配置mybatis环境的时候有一个标签:environment ,他有一个属性id;environmentId的就是这个id的值;
private static final int DEFAULT_MULTIPLIER = 37;
private static final int DEFAULT_HASHCODE = 17;
private final int multiplier;
private int hashcode;
private long checksum;
private int count;
private List<Object> updateList;
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
MetaObject metaObject = null;
Iterator var9 = parameterMappings.iterator();
while(var9.hasNext()) {
ParameterMapping parameterMapping = (ParameterMapping)var9.next();
if (parameterMapping.getMode() != ParameterMode.OUT) {
String propertyName = parameterMapping.getProperty();
Object value;
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
if (metaObject == null) {
metaObject = this.configuration.newMetaObject(parameterObject);
}
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
if (this.configuration.getEnvironment() != null) {
cacheKey.update(this.configuration.getEnvironment().getId());
}
return cacheKey;
}
}
public boolean equals(Object object) {
if (this == object) {
return true;
} else if (!(object instanceof CacheKey)) {
return false;
} else {
CacheKey cacheKey = (CacheKey)object;
if (this.hashcode == cacheKey.hashcode && this.checksum == cacheKey.checksum && this.count == cacheKey.count) {
for(int i = 0; i < this.updateList.size(); ++i) {
Object thisObject = this.updateList.get(i);
Object thatObject = cacheKey.updateList.get(i);
if (!ArrayUtil.equals(thisObject, thatObject)) {
return false;
}
}
return true;
} else {
return false;
}
}
}
public int hashCode() {
return this.hashcode;
}
简洁版总结
- springboot启动类添加@MapperScan,通过@Import+ImportBeanDefinitionRegistrar接口的方法向容器中添加mybatis相关的bean。
- 构建sqlSessionFactory
MybatisAutoConfiguration实现了InitializingBean,会创建SqlSessionFactoryBean, 然后通过SqlSessionFactoryBean.getObject()获取SqlSessionFactory,在getObject()方法中,如果sqlSessionFactory==null,会调用afterPropertiesSet()方法,在其中使用sqlSessionFactoryBuilder.build(targetConfiguration);创建一个SqlSessionFactory。(看5) - 解析mapper.xml文件
在通过sqlSessionFactoryBuilder.build创建sqlSessionFactory之前,会解析mapper.xml文件().(注意是先解析mapper.xml,最后在build SqlSessionFactory)
方法:xmlMapperBuilder.parse();
3.1 解析mapper文件,从mapper节点开始
解析parameterMap、resultMap(以便查处结果后和java类型中的字段进行匹配)、sql等节点(将select|insert|update|delete节点封装到一个MappedStatements中,key为id,value为MappedStatement)。
3.2 绑定namespace的mapper
根据namespace去加载类,如果不存在以该Class为key的mapper,则进行添加,将type(类)和以该type为参数构建的MapperProxyFactory作为键值对添加到knownMappers。
这里会生成一个完整的Configuration类,然后就可以用这个targetConfiguration去build SqlSessionFactory了。
-
构建SqlSessionTemplate
创建好sqlSessionFactory后,使用它去创建SqlSessionTemplate,SqlSessionTemplate 其实是 SqlSession 的一个增强代理类,会通过jdk动态代理创建
sqlSession的代理对象sqlSessionProxy。 -
查询时会进入MapperProxy的Invoke方法
mapperMethod.execute()
->
sqlSessionTemplate.selectList()
->
sqlSessionProxy.selectList()
->
SqlSessionInterceptor.invoke()
创建sqlSession ->
defaultSqlSession.selectList()
->
执行器executor.query(MappedStatement ms,......)
MappedStatements中获取MappedStatement ->
查本地缓存 ->queryFromDatabase
查询数据库 ->
创建数据库会话处理器StatementHandler
->
参数处理器DefaultParameterHandler.setParamers
设置参数 ->
结果集处理器ResultSetHandler.handleResultSets()
结果和java类进行匹配 -> 写入本地缓存 -> 返回
大白话总结
在springboot项目启动时会通过sqlSessionFactoryBuilder.build
创建sqlSessionFactory
,在创建过程中会解析mapper.xml
文件,之后会创建sqlSessionTemplate
,在创建sqlSessionTemplate
会创建sqlSessionProxy
,在进行sql查询时,会进入MapperProxy
的invoke
方法中调用sqlSessionTemplate
的查询方法,这里会调用SqlSessionInterceptor
的invoke()
方法创建sqlSession
对象,然后会创建executor
执行器和StatementHandler
数据库会话处理器进行参数设置进行数据库的查询,查询出结果后进行结果映射,这个过程主要是用到xml解析中的parameterMappings
和resultMappings
进行映射,之后写入本地缓存返回信息。