先上小结:
- 开始调用MybatisAutoConfiguration.sqlSessionFactory()方法。
- 构建SqlSessionFactoryBean类实例。
- 把数据源实例DataSource赋值给SqlSessionFactoryBean。
- 创建Configuration实例,赋值给SqlSessionFactoryBean。
- 读取mybatis配置文件,把mapperLocations参数赋值给SqlSessionFactoryBean。
- SqlSessionFactoryBean开始创建SqlSessionFactory实例。
- 处理typeAliasesPackage。
- configuration添加插件plugins到拦截器链interceptorChain中。
- 注册typeHandlersPackage和typeHandlers。
- 处理databaseIdProvider,这是数据库厂商标识。
- 初始化缓存cache。
- 创建transactionFactory,并由SqlSessionFactoryBean的environment,transactionFactory,dataSource组成configuration的environment。
- 开始处理mapperLocations,读取路径下的Mapper.xml文件,并开始解析。
- 注册Mapper.xml中<mapper>节点的namespace参数,然后开始处理/mapper/resultMap节点。
- 处理resultMap节点的id,type,extends,autoMapping参数。
- 处理resultMap节点的子节点,创建ResultMapping实例,组装property,column,javaType等参数。
- 组装ResultMap实例。resultMap节点处理完成。
- 处理/mapper/sql节点,加入configuration实例的sqlFragments这个Map中。
- 处理select|insert|update|delete节点。生成MappedStatement实例,添加到configuration的mappedStatements这个Map中。mapper节点处理完毕。
- 把xml文件对应的resource添加到已加载列表中,把nameSpace添加到已加载列表中。
- 由namespace代表的Mapper接口创建其代理工厂MapperProxyFactory,加入configuration的knownMapper中。
- 处理Mapper接口方法上的@Insert等注解,并转换成mappedStatements,然后添加到configuration的mappedStatements这个Map中,流程和从Mapper.xml中处理动态sql时差不多。
- 创建SqlSessionFactory实例,把configuration实例赋值给此实例。
正文:
经过一番调用Spring终于开始调用MybatisAutoConfiguration类的sqlSessionFactory()方法,开始构建SqlSessionFactory类实例:
@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()));
}
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 var4 = this.configurationCustomizers.iterator();
while(var4.hasNext()) {
ConfigurationCustomizer customizer = (ConfigurationCustomizer)var4.next();
customizer.customize(configuration);
}
}
factory.setConfiguration(configuration);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
return factory.getObject();
}
方法先是创建了一个SqlSessionFactoryBean实例,并把数据源实例DataSource赋值给factory。
然后创建Configuration实例,也赋值给factory。
下面给factory赋值了databaseIdProvider,如果没配置databaseId则不赋值。databaseId是多数据源的配置。
后面的TypeHandlersPackage相关的代码是自定义类型转化时用的。
再后面的一段代码值得关注一下,也就是这段:
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
这里的this.properties就是MybatisAutoConfiguration类的MybatisProperties参数,是这么定义的:
private final MybatisProperties properties;
这段代码的作用就是把MybatisProperties中的mapperLocations参数赋值给SqlSessionFactory。
MybatisProperties中的mapperLocations参数来自项目的配置文件,比如application.properties文件中配置:
mybatis.mapperLocations=classpath:mybatis/mappers/*.xml
至于这个配置如何加载到MybatisProperties参数中,在以后的文章中单独解析。
到了方法最后:
return factory.getObject();
就是由SqlSessionFactoryBean创建SqlSessionFactory实例的方法了,方法源码如下:
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
this.afterPropertiesSet();
}
return this.sqlSessionFactory;
}
初始化时,SqlSessionFactoryBean中的sqlSessionFactory就是null,所以要执行SqlSessionFactoryBean的afterPropertiesSet()方法,来创建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();
}
主要就是最后一行,调用this.buildSqlSessionFactory()方法:
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
XMLConfigBuilder xmlConfigBuilder = null;
Configuration configuration;
if (this.configuration != null) {
configuration = this.configuration;
if (configuration.getVariables() == null) {
configuration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
configuration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
}
configuration = new Configuration();
if (this.configurationProperties != null) {
configuration.setVariables(this.configurationProperties);
}
}
if (this.objectFactory != null) {
configuration.setObjectFactory(this.objectFactory);
}
if (this.objectWrapperFactory != null) {
configuration.setObjectWrapperFactory(this.objectWrapperFactory);
}
if (this.vfs != null) {
configuration.setVfsImpl(this.vfs);
}
String[] typeHandlersPackageArray;
String[] var4;
int var5;
int var6;
String packageToScan;
if (StringUtils.hasLength(this.typeAliasesPackage)) {
typeHandlersPackageArray = StringUtils.tokenizeToStringArray(this.typeAliasesPackage, ",; \t\n");
var4 = typeHandlersPackageArray;
var5 = typeHandlersPackageArray.length;
for(var6 = 0; var6 < var5; ++var6) {
packageToScan = var4[var6];
configuration.getTypeAliasRegistry().registerAliases(packageToScan, this.typeAliasesSuperType == null ? Object.class : this.typeAliasesSuperType);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
}
}
}
int var27;
if (!ObjectUtils.isEmpty(this.typeAliases)) {
Class[] var25 = this.typeAliases;
var27 = var25.length;
for(var5 = 0; var5 < var27; ++var5) {
Class<?> typeAlias = var25[var5];
configuration.getTypeAliasRegistry().registerAlias(typeAlias);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered type alias: '" + typeAlias + "'");
}
}
}
if (!ObjectUtils.isEmpty(this.plugins)) {
Interceptor[] var26 = this.plugins;
var27 = var26.length;
for(var5 = 0; var5 < var27; ++var5) {
Interceptor plugin = var26[var5];
configuration.addInterceptor(plugin);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered plugin: '" + plugin + "'");
}
}
}
if (StringUtils.hasLength(this.typeHandlersPackage)) {
typeHandlersPackageArray = StringUtils.tokenizeToStringArray(this.typeHandlersPackage, ",; \t\n");
var4 = typeHandlersPackageArray;
var5 = typeHandlersPackageArray.length;
for(var6 = 0; var6 < var5; ++var6) {
packageToScan = var4[var6];
configuration.getTypeHandlerRegistry().register(packageToScan);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
}
}
}
if (!ObjectUtils.isEmpty(this.typeHandlers)) {
TypeHandler[] var28 = this.typeHandlers;
var27 = var28.length;
for(var5 = 0; var5 < var27; ++var5) {
TypeHandler<?> typeHandler = var28[var5];
configuration.getTypeHandlerRegistry().register(typeHandler);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered type handler: '" + typeHandler + "'");
}
}
}
if (this.databaseIdProvider != null) {
try {
configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException var24) {
throw new NestedIOException("Failed getting a databaseId", var24);
}
}
if (this.cache != null) {
configuration.addCache(this.cache);
}
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
}
} catch (Exception var22) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, var22);
} finally {
ErrorContext.instance().reset();
}
}
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
if (!ObjectUtils.isEmpty(this.mapperLocations)) {
Resource[] var29 = this.mapperLocations;
var27 = var29.length;
for(var5 = 0; var5 < var27; ++var5) {
Resource mapperLocation = var29[var5];
if (mapperLocation != null) {
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception var20) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var20);
} finally {
ErrorContext.instance().reset();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
}
} else if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
return this.sqlSessionFactoryBuilder.build(configuration);
}
这个方法先是判断了configuration变量是否存在,configuration之前创建过了,此处略过。
下一部分代码处理了typeAliasesPackage,这部分的功能是处理实体类别名。根据配置的路径,扫描路径下的POJO类,并把这些类进行注册。
关于typeAliasesPackage:
这是个可配置的路径,也可以不配置,作用是给Mapper.xml文件中用到的POJO类起别名,resultType,resultMap,parameterType中的用到的类。
比如可以通过以下方式配置:
mybatis.type-aliases-package=com.macro.mall.mapper
如果不配置,在Mapper.xml文件中写POJO类的带路径全名也行。
后面有关this.plugins的代码是处理mybatis的插件,通过:
configuration.addInterceptor(plugin);
把插件加到configuration的interceptorChain变量中,interceptorChain是一个链状的结构。
后面关于this.typeHandlersPackage和this.typeHandlers的代码,处理了mybatis的类型处理器,typeHandlersPackage是通过路径的方式配置类型处理器,typeHandlers是直接指定类型处理器类。
再后面关于this.databaseIdProvider的代码,处理的是数据库厂商标识,在配置文件中配置了databaseIdProvider后,在Mapper.xml文件中给每个sql配置databaseId,可以指定使用的数据库类别。
后面关于this.cache的代码是处理mybatis缓存用的。配置的cache被加入configuration的caches参数,caches是Map<String, Cache>类别。
后面关于xmlConfigBuilder的代码,是用xmlConfigBuilder解析xml文件。
但是根据之前的代码,只要先前创建过了configuration,这个xmlConfigBuilder就会是null,也就是方法最开始的这一段:
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
configuration = this.configuration;
if (configuration.getVariables() == null) {
configuration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
configuration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
}
configuration不是null的时候,就不会设置xmlConfigBuilder了。
后面的代码,关于transactionFactory,是mybatis的事务工厂,默认由SpringManagedTransactionFactory类实现。
下一步,由SqlSessionFactoryBean的environment,transactionFactory,dataSource组成configuration的environment:
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
下一步,关于this.mapperLocations的处理,是对扫描到的Mapper.xml文件的加载。
这里的this.mapperLocations是一个Resource数组,指的就是之前扫描到的所有Mapper.xml文件对应的Resource。
对于每一个Resource,创建一个新的XMLMapperBuilder对象,然后调用其parse()方法:
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
对于configuration.isResourceLoaded()方法,configuration维护了一个loadedResources变量,是记录了加载好的Mapper.xml文件名的Set<String>变量。
对于没有被加载的Mapper.xml文件,首先调用configurationElement()方法,参数是xml文件中的<mapper>标签:
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
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);
}
}
可见,方法对Mapper.xml配置文件中的namespace存到builderAssistant中,然后分别处理了xml文件中的cache-ref节点,cache节点,/mapper/parameterMap节点,/mapper/resultMap节点,/mapper/sql节点,其中/mapper/resultMap节点的处理方法resultMapElements(),经过一番简单的调用和循环,最后调用如下方法:
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
resultMappings.addAll(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List<ResultFlag> flags = new ArrayList<ResultFlag>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
首先获取了resultMap节点的id,type,extends,autoMapping这四个参数。
extends参数可以用来继承另外一个resultMap节点。
autoMapping参数如果设为true,则自动匹配数据库字段名小写后的java属性,设为false或不设置则需要在resultMap节点中使用result节点手工匹配。
第二步,开始处理resultMap节点的子节点。处理的结果是创建了很多ResultMapping实例,并加入resultMappings列表中。
如果子节点是constructor节点,则调用processConstructorElement()方法处理。constructor节点是当resultMap的type类没有无参构造时使用的。
如果子节点是discriminator节点,则调用processDiscriminatorElement()节点处理。discriminator节点是鉴别器,利用某个字段的值,区分使用不同的子结果集。
如果子节点是其他节点,基本也就剩下result节点了,调用的是buildResultMappingFromContext()方法。下面来看一下这个方法:
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
String property;
if (flags.contains(ResultFlag.CONSTRUCTOR)) {
property = context.getStringAttribute("name");
} else {
property = context.getStringAttribute("property");
}
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
String nestedSelect = context.getStringAttribute("select");
String nestedResultMap = context.getStringAttribute("resultMap",
processNestedResultMappings(context, Collections.<ResultMapping> emptyList()));
String notNullColumn = context.getStringAttribute("notNullColumn");
String columnPrefix = context.getStringAttribute("columnPrefix");
String typeHandler = context.getStringAttribute("typeHandler");
String resultSet = context.getStringAttribute("resultSet");
String foreignColumn = context.getStringAttribute("foreignColumn");
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
Class<?> javaTypeClass = resolveClass(javaType);
@SuppressWarnings("unchecked")
Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
内容其实很简单,获取了result节点的property,column,javaType,jdbcType,select,resultMap,notNullColumn,columnPrefix,typeHandler,resultSet,foreignColumn,fetchType这些属性,这些属性不一定都是必填的,每个参数的具体功能这里就不写了。然后把这些属性都加到builderAssistant.buildResultMapping()方法中生成ResultMapping实例并返回。
回到对resultMap节点的处理,上面是第二步,下面第三步也就是最后一步,创建resultMapResolver实例,并调用其resolve()方法组装了ResultMap实例然后返回。
至此resultMap节点处理完毕。
回到XMLMapperBuilder的configurationElement()方法,在处理完了resultMap节点后,还要处理/mapper/sql节点:
sqlElement(context.evalNodes("/mapper/sql"));
其方法代码是:
private void sqlElement(List<XNode> list) throws Exception {
if (configuration.getDatabaseId() != null) {
sqlElement(list, configuration.getDatabaseId());
}
sqlElement(list, null);
}
根据是否存在dataBaseId,决定是否要先调用一次有dataBaseId的sqlElement()方法,注意这里不是if/else,也就是说,如果存在dataBaseId,sqlElement()方法实际上调用了两遍。
另外,dataBaseId来自前面关于dataBaseProvider的配置。
下面看看sqlElement()方法的代码:
private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
for (XNode context : list) {
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
id = builderAssistant.applyCurrentNamespace(id, false);
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
sqlFragments.put(id, context);
}
}
}
其中的databaseIdMatchesCurrent()方法其实是做了一个按id去重的工作,在一个xml文件中的sql节点,如果id相同,只加载第一个。处理好的sql节点加入configuration实例的sqlFragments这个map中。
至此/mapper/sql节点处理完毕。
再次回到XMLMapperBuilder的configurationElement()方法,处理完了/mapper/sql节点后,开始处理select|insert|update|delete节点:
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
方法代码是:
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
和之前/mapper/sql节点的处理思路差不多,也是按照是否有dataBaseId决定是否多调一次接口,其中buildStatementFromContext()方法的代码是:
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
创建了一个XMLStatementBuilder然后调用他的parseStatementNode()方法:
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
和之前的套路也差不多,获取所有能获取的参数值,另外对于insert节点来说,额外提供了对于selectKey的处理和useGeneratedKeys的处理,如果useGeneratedKeys参数设为true,则使用Jdbc3KeyGenerator类的实例作为KeyGenerator,这是一个用static和final标识的实例。
最后还是把这一大堆参数都传给builderAssistant.addMappedStatement()方法,生成MappedStatement实例,添加到configuration实例的mappedStatements这个map中。
这里的MappedStatement实例,在将来调用Mapper代理的具体某个方法的时候会用上,用来生成SqlCommand,进而生成MapperMethod,Mapper的动态代理调用的就是MapperMethod的execute()方法。
至此select|insert|update|delete节点处理完毕。
这样XMLMapperBuilder对/mapper节点就都处理完毕了。
回到XMLMapperBuilder的parse()方法:
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
处理完/mapper节点后,调用addLoadedResource()方法,把这个xml文件对应的resource字符串添加到已加载列表中,然后调用bindMapperForNamespace()方法:
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
}
作用有两个,
一是这行:
configuration.addLoadedResource("namespace:" + namespace);
把nameSpace字符串也添加到已加载列表中,这样对于一个xml来说在已加载列表中就有两项,大概是这样两个字符串:
namespace:com.test.dao.OrderMapper
file [D:\workspace\test\module\target\classes\mybatis\mappers\OrderMapper.xml]
二是把namespace代表的类加入configuration的knownMapper中,也就是这一行:
configuration.addMapper(boundType);
addMapper()方法的代码:
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
MapperRegistry类中有一个knownMappers 属性,类型是HashMap<Class<?>, MapperProxyFactory<?>>,用于保存已经加载好的动态代理,其中的key就是Mapper接口类,其addMapper()方法如下:
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
可见,方法一开始重新做了一次hasMapper()的校验。
然后调用knownMappers.put()方法添加对应Mapper接口类的动态代理。
动态代理的生成是new MapperProxyFactory<T>(type)
MapperProxyFactory类是Mapper接口类的代理工厂类,他的内容很简单:
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
可见,之前的type是MapperProxyFactory的一个参数,用来生成动态代理。
把MapperProxyFactory工厂类添加到knownMappers后,执行下面这两行:
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
这两行代码的功能处理Mapper类中,在方法上面写注解和动态sql的情况,比如这样的方法:
@Insert({
"insert into user (user_name)",
"values (#{userName,jdbcType=VARCHAR})"
})
int insert(User user);
还记得前面有段代码逻辑,是从Mapper.xml中获得动态sql,转换成MappedStatement并添加给configuration。这里的逻辑也是一样,只不过变成了从方法的注解中获取动态sql,对开发者来说,这只是两种不同的配置动态sql的方式。
通过以上流程,bindMapperForNamespace()方法执行完成,我们获得了Mapper接口类的动态代理工厂类,并把它添加到了configuration的knownMappers中,后面动态代理的生成和使用做好了准备。
回到XMLMapperBuilder的parse()方法,bindMapperForNamespace()执行完后,最后了三个方法:
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
是把之前创建失败的ResultMap,CacheRefs,Statement,再次尝试解析,然后从失败列表中删除。
至此XMLMapperBuilder的parse()方法结束。
接下来回到SqlSessionFactoryBean的buildSqlSessionFactory()方法,来到最后的:
return this.sqlSessionFactoryBuilder.build(configuration);
经过上面的一顿操作,已经把xml文件里的元素都解析并且添加到了configuration实例中,最后就是调用build()方法,生成一个SqlSessionFactory实例:
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
SqlSessionFactory里的变量其实就只有这个configuration。
于是,SqlSessionFactory实例创建成功,当我们在代码中使用mybatis时,就会使用这个实例。
(本文结束)