核心流程-初始化阶段
一、核心流程-初始化阶段
Mybatis的核心流程三大阶段是:初始化–>动态代理–>数据读写阶段,本文主要分析初始化阶段。初始化阶段主要是完成XML配置文件和注解配置信息的读取,创建全局单例的Configuration配置对象,完成各部分的初始化工作,具体的创建过程需要三个核心类来完成解析。
二、核心类
2.1 创建Configuration的三个核心类
类名 作用 XmlConfigBuilder 加载解析主配置文件 XmlMapperBuilder 加载解析mapper映射文件中非SQL节点部分 XmlStatementBuilder 加载解析mapper映射文件中SQL节点部分
这三个类都比较复杂,使用到了建造者模式,关于建造者模式可以参考:01-创建型模式(上) 上面三个类共同完成全局配置文件的加载解析,来构建Configuration配置对象。这三个类看起来并没有使用到建造者模式的流式风格,但是借鉴了建造者模式的思想,在CacheBuilder里面的build方法是典型的建造者模式加载核心配置文件来创建全局的配置对象。
2.2 其他核心类
类名 作用 Configuration 单例对象,存在于程序的整个生命周期,包含所有的配置信息(初始化核心就是创建该对象) MapperRegistry mapper接口动态代理的注册中心,注册的就是Java接口 MapperProxy 动态代理类,实现了InvocationHandler接口,用于为接口生成动态代理实例,实例就是MapperProxy类型的。 MapperProxyFactory 用于生成MapperProxy实例 ResultMap 解析映射配置文件中的resultMap节点,内部包含一个ResultMapping类型的List,里面就封装了id,result等子元素 MappedStatement 存储映射文件中的insert SqlSource 映射文件中的SQL语句会解析成SqlSource对象,解析sqlSource后得到的sql语句只包含占位符,可以直接交给DB执行
三、Configuration源码解析
3.1 Configuration属性
public class Configuration {
protected Environment environment;
protected boolean safeRowBoundsEnabled = false ;
protected boolean safeResultHandlerEnabled = true ;
protected boolean mapUnderscoreToCamelCase = false ;
protected boolean aggressiveLazyLoading = true ;
protected boolean multipleResultSetsEnabled = true ;
protected boolean useGeneratedKeys = false ;
protected boolean useColumnLabel = true ;
protected boolean cacheEnabled = true ;
protected boolean callSettersOnNulls = false ;
protected boolean useActualParamName = true ;
protected String logPrefix;
protected Class< ? extends Log > logImpl;
protected Class< ? extends VFS > vfsImpl;
protected LocalCacheScope localCacheScope = LocalCacheScope. SESSION;
protected JdbcType jdbcTypeForNull = JdbcType. OTHER;
protected Set< String> lazyLoadTriggerMethods = new HashSet < String> ( Arrays. asList ( new String [ ] { "equals" , "clone" , "hashCode" , "toString" } ) ) ;
protected Integer defaultStatementTimeout;
protected Integer defaultFetchSize;
protected ExecutorType defaultExecutorType = ExecutorType. SIMPLE;
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior. PARTIAL;
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior. NONE;
protected Properties variables = new Properties ( ) ;
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory ( ) ;
protected ObjectFactory objectFactory = new DefaultObjectFactory ( ) ;
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory ( ) ;
protected boolean lazyLoadingEnabled = false ;
protected ProxyFactory proxyFactory = new JavassistProxyFactory ( ) ;
protected String databaseId;
protected Class< ? > configurationFactory;
protected final MapperRegistry mapperRegistry = new MapperRegistry ( this ) ;
protected final InterceptorChain interceptorChain = new InterceptorChain ( ) ;
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry ( ) ;
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry ( ) ;
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry ( ) ;
protected final Map< String, MappedStatement> mappedStatements = new StrictMap < MappedStatement> ( "Mapped Statements collection" ) ;
protected final Map< String, Cache> caches = new StrictMap < Cache> ( "Caches collection" ) ;
protected final Map< String, ResultMap> resultMaps = new StrictMap < ResultMap> ( "Result Maps collection" ) ;
protected final Map< String, ParameterMap> parameterMaps = new StrictMap < ParameterMap> ( "Parameter Maps collection" ) ;
protected final Map< String, KeyGenerator> keyGenerators = new StrictMap < KeyGenerator> ( "Key Generators collection" ) ;
protected final Set< String> loadedResources = new HashSet < String> ( ) ;
protected final Map< String, XNode> sqlFragments = new StrictMap < XNode> ( "XML fragments parsed from previous mappers" ) ;
protected final Collection< XMLStatementBuilder> incompleteStatements = new LinkedList < XMLStatementBuilder> ( ) ;
protected final Collection< CacheRefResolver> incompleteCacheRefs = new LinkedList < CacheRefResolver> ( ) ;
protected final Collection< ResultMapResolver> incompleteResultMaps = new LinkedList < ResultMapResolver> ( ) ;
protected final Collection< MethodResolver> incompleteMethods = new LinkedList < MethodResolver> ( ) ;
public Configuration ( Environment environment) {
this ( ) ;
this . environment = environment;
}
public Configuration ( ) {
typeAliasRegistry. registerAlias ( "JDBC" , JdbcTransactionFactory. class ) ;
typeAliasRegistry. registerAlias ( "MANAGED" , ManagedTransactionFactory. class ) ;
typeAliasRegistry. registerAlias ( "JNDI" , JndiDataSourceFactory. class ) ;
typeAliasRegistry. registerAlias ( "POOLED" , PooledDataSourceFactory. class ) ;
typeAliasRegistry. registerAlias ( "UNPOOLED" , UnpooledDataSourceFactory. class ) ;
typeAliasRegistry. registerAlias ( "PERPETUAL" , PerpetualCache. class ) ;
typeAliasRegistry. registerAlias ( "FIFO" , FifoCache. class ) ;
typeAliasRegistry. registerAlias ( "LRU" , LruCache. class ) ;
typeAliasRegistry. registerAlias ( "SOFT" , SoftCache. class ) ;
typeAliasRegistry. registerAlias ( "WEAK" , WeakCache. class ) ;
typeAliasRegistry. registerAlias ( "DB_VENDOR" , VendorDatabaseIdProvider. class ) ;
typeAliasRegistry. registerAlias ( "XML" , XMLLanguageDriver. class ) ;
typeAliasRegistry. registerAlias ( "RAW" , RawLanguageDriver. class ) ;
typeAliasRegistry. registerAlias ( "SLF4J" , Slf4jImpl. class ) ;
typeAliasRegistry. registerAlias ( "COMMONS_LOGGING" , JakartaCommonsLoggingImpl. class ) ;
typeAliasRegistry. registerAlias ( "LOG4J" , Log4jImpl. class ) ;
typeAliasRegistry. registerAlias ( "LOG4J2" , Log4j2Impl. class ) ;
typeAliasRegistry. registerAlias ( "JDK_LOGGING" , Jdk14LoggingImpl. class ) ;
typeAliasRegistry. registerAlias ( "STDOUT_LOGGING" , StdOutImpl. class ) ;
typeAliasRegistry. registerAlias ( "NO_LOGGING" , NoLoggingImpl. class ) ;
typeAliasRegistry. registerAlias ( "CGLIB" , CglibProxyFactory. class ) ;
typeAliasRegistry. registerAlias ( "JAVASSIST" , JavassistProxyFactory. class ) ;
languageRegistry. setDefaultDriverClass ( XMLLanguageDriver. class ) ;
languageRegistry. register ( RawLanguageDriver. class ) ;
}
}
四、解析对应图
上图展示了配置文件和Configuration对象之间的对应关系,每一个配置都会映射到Configuration对象内部的属性。
五、初始化分析
5.1 XMLConfigBuilder
示例如下,初始化过程在代码的角度看,只有SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream) 这一句代码,我们从这一句代码分析其背后初始化所走过的流程。
public void test() throws IOException {
String resource = "mybatis/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 1.读取mybatis配置文件创SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2.从SqlSessionFactory获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3.获取对应mapper
PlayerDao mapper = sqlSession.getMapper(PlayerDao.class);
// 4.执行查询语句并返回结果
Player player = mapper.findPlayerById(1);
System.out.println(player);
inputStream.close();
}
5.1.1 入口方法build
SqlSessionFactoryBuilder#build(java.io.InputStream) // new SqlSessionFactoryBuilder().build(inputStream) ,最后调用的方法是:SqlSessionFactoryBuilder#buildbuild(InputStream inputStream, String environment, Properties properties),
public SqlSessionFactory build ( InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder ( inputStream, environment, properties) ;
return build ( parser. parse ( ) ) ;
} catch ( Exception e) {
throw ExceptionFactory. wrapException ( "Error building SqlSession." , e) ;
} finally {
ErrorContext. instance ( ) . reset ( ) ;
try {
inputStream. close ( ) ;
} catch ( IOException e) {
}
}
}
public SqlSessionFactory build ( Configuration config) {
return new DefaultSqlSessionFactory ( config) ;
}
5.1.2 XMLConfigBuilder#parse
XMLConfigBuilder#parse方法是配置解析的核心方法
public Configuration parse ( ) {
if ( parsed) {
throw new BuilderException ( "Each XMLConfigBuilder can only be used once." ) ;
}
parsed = true ;
parseConfiguration ( parser. evalNode ( "/configuration" ) ) ;
return configuration;
}
5.1.3 XMLConfigBuilder#parseConfiguration
XMLConfigBuilder#parseConfiguration方法完成全部的配置解析主流程
private void parseConfiguration ( XNode root) {
try {
Properties settings = settingsAsPropertiess ( root. evalNode ( "settings" ) ) ;
propertiesElement ( root. evalNode ( "properties" ) ) ;
loadCustomVfs ( settings) ;
typeAliasesElement ( root. evalNode ( "typeAliases" ) ) ;
pluginElement ( root. evalNode ( "plugins" ) ) ;
objectFactoryElement ( root. evalNode ( "objectFactory" ) ) ;
objectWrapperFactoryElement ( root. evalNode ( "objectWrapperFactory" ) ) ;
reflectorFactoryElement ( root. evalNode ( "reflectorFactory" ) ) ;
settingsElement ( settings) ;
environmentsElement ( root. evalNode ( "environments" ) ) ;
databaseIdProviderElement ( root. evalNode ( "databaseIdProvider" ) ) ;
typeHandlerElement ( root. evalNode ( "typeHandlers" ) ) ;
mapperElement ( root. evalNode ( "mappers" ) ) ;
} catch ( Exception e) {
throw new BuilderException ( "Error parsing SQL Mapper Configuration. Cause: " + e, e) ;
}
}
我们看到这个方法的解析流程都很类似,最后面引入了XMLMapperBuilder解析节点,我们在前面挑一个解析typeAliases的看看细节,其他的就不一一分析了,然后进入5.2小节XMLMapperBuilder解析阶段的分析
5.1.4 XMLConfigBuilder#typeAliasesElement
typeAliasesElement解析别名节点,typeAliases有两种配置方式,如下所示:
< typeAliases>
< package name= "com.intellif.mozping.entity" / >
< typeAlias alias= "Product" type= "com.intellif.mozping.entity.Product" / >
< / typeAliases>
XMLConfigBuilder#typeAliasesElement
private void typeAliasesElement ( XNode parent) {
if ( parent != null) {
for ( XNode child : parent. getChildren ( ) ) {
if ( "package" . equals ( child. getName ( ) ) ) {
String typeAliasPackage = child. getStringAttribute ( "name" ) ;
configuration. getTypeAliasRegistry ( ) . registerAliases ( typeAliasPackage) ;
} else {
String alias = child. getStringAttribute ( "alias" ) ;
String type = child. getStringAttribute ( "type" ) ;
try {
Class< ? > clazz = Resources. classForName ( type) ;
if ( alias == null) {
typeAliasRegistry. registerAlias ( clazz) ;
} else {
typeAliasRegistry. registerAlias ( alias, clazz) ;
}
} catch ( ClassNotFoundException e) {
throw new BuilderException ( "Error registering typeAlias for '" + alias + "'. Cause: " + e, e) ;
}
}
}
}
}
5.2 XMLMapperBuilder
最前面我们说过XMLMapperBuilder是用于解析Mapper.xml映射文件的,因此在前面的XMLConfigBuilder#parseConfiguration方法的解析流程最后一个步骤是mapperElement,该方法内部XMLMapperBuilder就会登场进行mapper.xml映射文件的解析工作,我们先看看这个方法:
5.2.1 XMLConfigBuilder#mapperElement
XMLConfigBuilder#mapperElement方法解析节点,这个方法包含解析mapper节点的主流程,但是解析的细节还看不到,解析的细节在后面的XMLMapperBuilder#parse里面,我们先通过这个方法看一下解析的主流程,mappers有多种配置方式,如下所示:
< mappers>
< ! -- 直接映射到相应的mapper文件 -- >
< mapper resource= "mybatis/mapper/EmployeeMapper.xml" / >
< mapper url= "xx" / >
< mapper class = "yy" / >
< package name= "com.intellif.mozping" / >
< / mappers>
XMLConfigBuilder#mapperElement
private void mapperElement ( XNode parent) throws Exception {
if ( parent != null) {
for ( XNode child : parent. getChildren ( ) ) {
if ( "package" . equals ( child. getName ( ) ) ) {
String mapperPackage = child. getStringAttribute ( "name" ) ;
configuration. addMappers ( mapperPackage) ;
} else {
String resource = child. getStringAttribute ( "resource" ) ;
String url = child. getStringAttribute ( "url" ) ;
String mapperClass = child. getStringAttribute ( "class" ) ;
if ( resource != null && url == null && mapperClass == null) {
ErrorContext. instance ( ) . resource ( resource) ;
InputStream inputStream = Resources. getResourceAsStream ( resource) ;
XMLMapperBuilder mapperParser = new XMLMapperBuilder ( inputStream, configuration, resource, configuration. getSqlFragments ( ) ) ;
mapperParser. parse ( ) ;
} else if ( resource == null && url != null && mapperClass == null) {
ErrorContext. instance ( ) . resource ( url) ;
InputStream inputStream = Resources. getUrlAsStream ( url) ;
XMLMapperBuilder mapperParser = new XMLMapperBuilder ( inputStream, configuration, url, configuration. getSqlFragments ( ) ) ;
mapperParser. parse ( ) ;
} else if ( resource == null && url == null && mapperClass != null) {
Class< ? > mapperInterface = Resources. classForName ( mapperClass) ;
configuration. addMapper ( mapperInterface) ;
} else {
throw new BuilderException ( "A mapper element may only specify a url, resource or class, but not more than one." ) ;
}
}
}
}
}
结合代码注释和配置文件的结构,流程还是比较清晰的,这里面会将mappers里面的节点信息全部注册到Configuration所持有的MapperRegistry对象里面去,2.2说过MapperRegistry是mapper接口动态代理的注册中心,总而言之Configuration里包含所有的配置信息,最后一种情况使用的是configuration.addMapper(mapperInterface);方法,其实前面几种情况最后也是走的这个方法,这里我们看到了主体流程,5.2.1我们开始看解析的具体过程分析。
5.2.2 XMLMapperBuilder#parse
XMLMapperBuilder#parse进入了XMLMapperBuilder解析mapper节点的详细流程,最后把mapper节点信息保存到Configuration的MapperRegistry里面去,并且会对Mapper.xml映射文件的内存进行解析,映射文件的内容是非常复杂的,我们慢慢跟进看
public void parse ( ) {
if ( ! configuration. isResourceLoaded ( resource) ) {
configurationElement ( parser. evalNode ( "/mapper" ) ) ;
configuration. addLoadedResource ( resource) ;
bindMapperForNamespace ( ) ;
}
parsePendingResultMaps ( ) ;
parsePendingChacheRefs ( ) ;
parsePendingStatements ( ) ;
}
5.2.3 XMLMapperBuilder#configurationElement
configurationElement方法是解析mapper映射文件的主流程,我们可以看到每种类型节点的解析过程,注意第八步是解析sql信息,我们前面说过,XMLMapperBuilder解析mapper文件但是不解析sql节点,sql节点是由XmlStatementBuilder来负责的,后面我们会看到XmlStatementBuilder的作用
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. Cause: " + e, e) ;
}
}
5.2.4 XMLMapperBuilder#bindMapperForNamespace
bindMapperForNamespace负责将mapper映射文件对应的java接口类(这个接口类的名称就是mapper文件里面的namespace)注册到Configuration的MapperRegistry里面,这代表该接口已经注册,这也是5.2.1主流程的目的
private void bindMapperForNamespace ( ) {
String namespace = builderAssistant. getCurrentNamespace ( ) ;
if ( namespace != null) {
Class< ? > boundType = null;
try {
boundType = Resources. classForName ( namespace) ;
} catch ( ClassNotFoundException e) {
}
if ( boundType != null) {
if ( ! configuration. hasMapper ( boundType) ) {
configuration. addLoadedResource ( "namespace:" + namespace) ;
configuration. addMapper ( boundType) ;
}
}
}
}
5.2.5 XMLMapperBuilder#parsePendingResultMaps
private void parsePendingResultMaps ( ) {
Collection< ResultMapResolver> incompleteResultMaps = configuration. getIncompleteResultMaps ( ) ;
synchronized ( incompleteResultMaps) {
Iterator< ResultMapResolver> iter = incompleteResultMaps. iterator ( ) ;
while ( iter. hasNext ( ) ) {
try {
iter. next ( ) . resolve ( ) ;
iter. remove ( ) ;
} catch ( IncompleteElementException e) {
}
}
}
}
parsePendingChacheRefs(),parsePendingStatements()和parsePendingResultMaps方法是类似的,就不多解析了,这一步我们主要是梳理XMLMapperBuilder#parse方法,并对里面几个关键的方法做了大概的跟踪,但是底层的实现逻辑我们暂时不跟进,否则内容太多。
5.3 XmlStatementBuilder
前面的XMLMapperBuilder我们跟了一部分源码,看到了XMLMapperBuilder解析mapper映射文件的主体流程,现在我们看XmlStatementBuilder是如何解析mapper中的SQL语句的,解析sql语句属于解析mapper文件的一部分,我们XMLMapperBuilder#configurationElement的 最后一个流程方法buildStatementFromContext里面跟进去,就可以看到他的身影,我们跟进去看看主流程:
5.3.1 XMLStatementBuilder#parseStatementNode
parseStatementNode方法是解析sql语句的主流程方法
buildStatementFromContext ( context. evalNodes ( "select|insert|update|delete" ) ) ;
private void buildStatementFromContext ( List< XNode> list) {
if ( configuration. getDatabaseId ( ) != null) {
buildStatementFromContext ( list, configuration. getDatabaseId ( ) ) ;
}
buildStatementFromContext ( list, null) ;
}
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) ;
}
}
}
5.3.2 XMLStatementBuilder#parseStatementNode
XMLStatementBuilder#parseStatementNode是解析sql节点的核心方法,该方法是解析sql节点的主流程,包括四种sql节点,解析完毕之后会将sql语句信息封装之后,注册到Configuration对象里面的mappedStatements集合里面去,2.2说过MappedStatement存储了映射文件中的insert|delete|update|select节点的重要信息
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 ) ;
XMLIncludeTransformer includeParser = new XMLIncludeTransformer ( configuration, builderAssistant) ;
includeParser. applyIncludes ( context. getNode ( ) ) ;
processSelectKeyNodes ( id, parameterTypeClass, langDriver) ;
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) )
? new Jdbc3KeyGenerator ( ) : new NoKeyGenerator ( ) ;
}
builderAssistant. addMappedStatement ( id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets) ;
}
5.3.3 MapperBuilderAssistant#addMappedStatement()
在XMLStatementBuilder#parseStatementNode的最后一步,通过调用builderAssistant.addMappedStatement方法来完成MappedStatement对象实例化和注册到Configuration对象的mappedStatements集合,sql语句解析之后,对应到的java的数据结构类是MappedStatement,它是保存sql语句的数据结构。sql语句的节点的全部配置,都能够在MappedStatement中找到对应的属性。到此XmlConfigBuilder、XmlMapperBuilder和XmlStatementBuilder三者分工协作,依次完成了主配置文件、mapper映射文件(不含sql信息)和mapper文件sql节点信息配置的加载,最终都把这些信息放到了Configuration这个大配置对象里面去了,到此处虽然还有很多细节没有分析,但是基本上初始化的流程已经看得出个大概了。 下面是代码细节:
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" ) ;
}
id = applyCurrentNamespace ( id, false ) ;
boolean isSelect = sqlCommandType == SqlCommandType. SELECT;
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) ;
}
MappedStatement statement = statementBuilder. build ( ) ;
configuration. addMappedStatement ( statement) ;
return statement;
}
六、补充
6.1 resultMap加载
在5.2.3 XMLMapperBuilder#configurationElement解析mapper.xml映射文件的主流程的第6步是解析resultMap配置节点。resultMap的数据结构相对比较复杂,设计的映射关系,属性比较多,比如id,type,里面又可能有构造方法节点,id,result等。因此加载解析也比较复杂在,Configuration对象里面有一个resultMap类型的Map对象来保存全部的resultMap,key是namespace+id,保证全局唯一。解析后对应的数据结构,可以参考org.apache.ibatis.mapping.ResultMap这个类。属性展示如下,这个类代码不多,可以参考源码理解。
private Class<?> type;
private List<ResultMapping> resultMappings;
private List<ResultMapping> idResultMappings;
private List<ResultMapping> constructorResultMappings;
private List<ResultMapping> propertyResultMappings;
private Set<String> mappedColumns;
private Discriminator discriminator;
private boolean hasNestedResultMaps;
private boolean hasNestedQueries;
//是否开启自定映射
private Boolean autoMapping;
< resultMap id = " BaseResultMap" type = " Player" >
< id column = " id" property = " id" jdbcType = " INTEGER" />
< result column = " playName" property = " playName" jdbcType = " VARCHAR" />
< result column = " playNo" property = " playNo" jdbcType = " INTEGER" />
< result column = " team" property = " team" jdbcType = " VARCHAR" />
< result column = " height" property = " height" jdbcType = " VARCHAR" />
</ resultMap>
加载resuleMap的流程在XMLMapperBuilder#resultMapElement()方法内,代码如下,只有部分注释,有兴趣可以继续研究
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;
}
}
6.2 Mybais的初始化流程图
6.3 小结
更多的节点配置信息源码,有兴趣可以自行解读,代码量比较大,但是熟悉了Mybatis框架和配置之后,总体结构还是比较清晰的,阅读这些源码本身难度不是很大,有助于帮助我们了解Mybatis框架,关键还是理解里面不同模块运用的设计思想和设计模式。下一篇文章我们分析核心流程的第二阶段—动态代理阶段