1.前言2.类 `SqlSessionFactoryBean`
2.1.实现了 `FactoryBean` 接口的 `getObject()`2.2.`buildSqlSessionFactory()`2.3.解析 `mapper.xml` 文件的 `parse()`2.4.解析 `mapper.xml` 文件的 `configurationElement()`2.5.解析 `mapper.xml` 节点 `statementParser.parseStatementNode()`2.6.绑定 `namespace` 的 `mapper` 的 `bindMapperForNamespace` 3.类 `SqlSessionFactoryBean` 小结
1.前言本篇文章是 Spring 整合 MyBatis 原理的第二篇文章,上一篇文章 在这里 ,我们来继续学习 Spring 整合 MyBatis 原理
2.类 SqlSessionFactoryBean对于 SqlSessionFactoryBean 来说,实现了 InitializingBean 和 FactoryBean 接口
FactoryBean 可以自定义创建实例 bean 的方法,只需要实现它的 getObject() 方法InitializingBean 则是会在 bean 初始化阶段被调用 2.1.实现了 FactoryBean 接口的 getObject()
public class SqlSessionFactoryBean implements FactoryBean2.2.buildSqlSessionFactory(), InitializingBean, ApplicationListener { @Override public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { // 如果之前没有构建,则这边也会调用afterPropertiesSet进行构建操作 afterPropertiesSet(); } return this.sqlSessionFactory; } @Override public void afterPropertiesSet() throws Exception { // 省略部分代码...... // 构建sqlSessionFactory this.sqlSessionFactory = buildSqlSessionFactory(); } }
主要做了几件事:
对我们配置的参数进行相应解析使用配置的参数构建一个 Configuration使用 Configuration 新建一个 DefaultSqlSessionFactory
这边的核心内容是对于 mapperLocations 的解析,如下代码
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
// 省略部分代码......
// 5.mapper处理(最重要)
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
} else {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
// 5.1 新建XMLMapperBuilder
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
// 5.2 解析mapper文件
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.");
}
// 6.使用 targetConfiguration 构建 DefaultSqlSessionFactory
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
2.3.解析 mapper.xml 文件的 parse()
public void parse() {
// 1.如果 resource 没被加载过才进行加载
if (!configuration.isResourceLoaded(resource)) {
// 1.1 解析 mapper 文件
configurationElement(parser.evalNode("/mapper"));
// 1.2 将 resource 添加到已加载列表
configuration.addLoadedResource(resource);
// 1.3 绑定 namespace 的 mapper
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
2.4.解析 mapper.xml 文件的 configurationElement()
private void configurationElement(XNode context) {
try {
// 1.获取namespace属性
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 2.设置currentNamespace属性
builderAssistant.setCurrentNamespace(namespace);
// 3.解析parameterMap、resultMap、sql等节点
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
// 4.解析增删改查节点,封装成 Statement
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);
}
}
private void buildStatementFromContext(List list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
// 解析增删改查节点,封装成Statement
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List list, String requiredDatabaseId) {
for (XNode context : list) {
// 1.构建XMLStatementBuilder
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 2.解析节点
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
XNode 的 Debug 示意图如下
每个 XNode 都相当于如下的一个 SQL,下面封装的每个 MappedStatement 可以理解就是每个 SQL
2.5.解析 mapper.xml 节点 statementParser.parseStatementNode()select id, name, password, age from user where id = #{id,jdbcType=INTEGER}
public void parseStatementNode() {
// 省略所有的属性解析......
// 将解析出来的所有参数添加到 mappedStatements 缓存
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
// MapperBuilderAssistant.java
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");
}
// 1.将 id 填充上 namespace,例如:id 原为 queryByPrimaryKey 现变成
// id = com.joonwhee.open.mapper.UserPOMapper.queryByPrimaryKey
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 2.使用参数构建 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);
}
// 3.使用 MappedStatement.Builder 构建 MappedStatement
MappedStatement statement = statementBuilder.build();
// 4.将 MappedStatement 添加到缓存
configuration.addMappedStatement(statement);
return statement;
}
id = applyCurrentNamespace(id, false):参数 id 是原方法名,该方法的作用是给 id 重新加上 Mapper 接口的全限类名如下图所示,id 是原方法名称 selectByPrimaryKey加上 Mapper 接口的全限类名之后为:com.atguigu.mapper.ProductInfoMapper.selectByPrimaryKey
该方法会将节点的属性解析后封装成 MappedStatement,放到 mappedStatements 缓存中key 为 id。例如:
key 为:com.atguigu.mapper.ProductInfoMapper.selectByPrimaryKeyvalue 为:MappedStatement
2.6.绑定 namespace 的 mapper 的 bindMapperForNamespaceprivate void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class> boundType = null;
try {
// 1.解析 namespace 对应的绑定类型
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {}
if (boundType != null && !configuration.hasMapper(boundType)) {
// 2.boundType不为空,并且configuration还没有添加boundType,
// 则将namespace添加到已加载列表,将boundType添加到knownMappers缓存
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
public void addMapper(Class type) {
mapperRegistry.addMapper(type);
}
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 {
// 将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
3.类 SqlSessionFactoryBean 小结- 解析处理所有属性参数构建 Configuration ,使用 Configuration 新建 DefaultSqlSessionFactory解析 mapper.xml 文件,将 mapper.xml 文件中的每个 SQL 封装成 MappedStatement,放到 mappedStatements 缓存(StrictMap)中,key 为 id,例如:com.atguigu.mapper.ProductInfoMapper.selectByPrimaryKey,value 为 MappedStatement将解析过的 mapper.xml 文件的 namespace 信息放到 knownMappers 缓存中,key 为 namespace 对应的 class,value 为 MapperProxyFactory
那么 knownMappers 缓存中的 namespace 信息何时取出来执行呢?请看 这篇文章 的 3.1.6 节点处的 MapperRegistry 类中的 getMapper() 方法



