栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring整合MyBatis原理之SqlSessionFactoryBean(二)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring整合MyBatis原理之SqlSessionFactoryBean(二)

目录

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 FactoryBean, 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();
	}
}	
2.2.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


    select id, name, password, age
    from user
    where id = #{id,jdbcType=INTEGER}

2.5.解析 mapper.xml 节点 statementParser.parseStatementNode()
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 的 bindMapperForNamespace
private 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() 方法

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/755855.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号