Java面试复盘:springboot项目中Mybatis的执行流程源码解析

本博文只为学习记录方便后期回顾,大家可直接查看参考链接学习,文中如有错误,欢迎指正!


参考链接:
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);
}

在这里插入图片描述
这三个方法所完成的目标为:

  1. 解析select|insert|update|delete节点,
  2. 构建出一个MappedStatement
  3. 然后以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;
    }

简洁版总结

  1. springboot启动类添加@MapperScan,通过@Import+ImportBeanDefinitionRegistrar接口的方法向容器中添加mybatis相关的bean。
  2. 构建sqlSessionFactory
    MybatisAutoConfiguration实现了InitializingBean,会创建SqlSessionFactoryBean, 然后通过SqlSessionFactoryBean.getObject()获取SqlSessionFactory,在getObject()方法中,如果sqlSessionFactory==null,会调用afterPropertiesSet()方法,在其中使用sqlSessionFactoryBuilder.build(targetConfiguration);创建一个SqlSessionFactory。(看5)
  3. 解析mapper.xml文件

在通过sqlSessionFactoryBuilder.build创建sqlSessionFactory之前,会解析mapper.xml文件().(注意是先解析mapper.xml,最后在build SqlSessionFactory)
方法:xmlMapperBuilder.parse();

3.1 解析mapper文件,从mapper节点开始
解析parameterMapresultMap(以便查处结果后和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了。

  1. 构建SqlSessionTemplate
    创建好sqlSessionFactory后,使用它去创建SqlSessionTemplate,SqlSessionTemplate 其实是 SqlSession 的一个增强代理类,会通过jdk动态代理创建
    sqlSession的代理对象sqlSessionProxy

  2. 查询时会进入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查询时,会进入MapperProxyinvoke方法中调用sqlSessionTemplate的查询方法,这里会调用SqlSessionInterceptorinvoke()方法创建sqlSession对象,然后会创建executor执行器和StatementHandler数据库会话处理器进行参数设置进行数据库的查询,查询出结果后进行结果映射,这个过程主要是用到xml解析中的parameterMappingsresultMappings进行映射,之后写入本地缓存返回信息。

  • 17
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值