最近我又暂时到其他项目组支援去了,使用的是spring boot,ORM框架使用的jpa,自己以前学习过一点皮毛,所以上手开发还是没问题的,相对来讲空闲时间比较多。所以我觉得有必要好好看下Mybatis的源码,毕竟从实际开发情况来看,使用Mybatis依然是主流。这次学习目的不是去深究源码,而是理清spring下Mybatis到底是怎样的一个执行过程,以前也仅仅知道Mybatis使用的也是动态代理,其底层使用的是JDBC而已,具体的执行过程却不怎么清楚,所以今天就来看看Mybatis的真实面目。
一、Mybatis执行入口
在我们的项目中Mybatis主要有两个部分,一个是mapper接口类,一个是mapper.xml,为了方便表述,下文中用mapper代替mapper接口类,xml代替mapper.xml文件。这两个文件是通过xml文件的namespace进行映射的,也就是说通过xml的namespace可以唯一确定一个mapper。而通过xml文件中的select | update | delete | insert标签的id值也可以唯一确定一个mapper的方法。详细内容后面再说,我们先看项目启动时Mybatis做了那些工作。
spring boot启动过程中会有一个非常重要的配置,就是MybatisAutoConfiguration,这个类是mybatis自动配置,也就是说这个类相当与是自动配置的和@EnableAutoConfiguration一样,不过它是针对mybatis的。根据spring boot的启动过程,MybatisAutoConfiguration会在容器刷新时执行构造方法,下面是部分代码:
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration {
private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
private final MybatisProperties properties;
private final Interceptor[] interceptors;
private final ResourceLoader resourceLoader;
private final DatabaseIdProvider databaseIdProvider;
private final List<ConfigurationCustomizer> configurationCustomizers;
public MybatisAutoConfiguration(MybatisProperties properties,ObjectProvider<Interceptor[]> interceptorsProvider,
ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider,
ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
this.properties = properties;
this.interceptors = interceptorsProvider.getIfAvailable();
this.resourceLoader = resourceLoader;
this.databaseIdProvider = databaseIdProvider.getIfAvailable();
this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
}
...省略代码
}
这个类上的注解还是很多的,我们一一看下。@ConditionalOnClass,表示的是classpath下必须有制定的类文件才会执行,即必须有SqlSessionFactory、SqlSessionFactoryBean这两类;@ConditionalOnBean表示的是必须在制定bean存在的情况下才会执行,即DataSource必须作为bean注入到spring容器中之后;@EnableConfigurationProperties表示允许指定的类自动注入spring中,即一种更方便的形式注入MybatisProperties,不需要显性使用@Bean注解即可注入;@AutoConfigureAfter表示必须在指定的配置完成配置后执行,也就是必须在完成DataSourceAutoConfiguration后才执行MybatisAutoConfiguration。
我们看下MybatisAutoConfiguration构造函数,因为作为bean肯定会执行它的构造函数(其实在这之前它的setBeanFactory和setResourceLoader方法会先执),这里主要会完成其成员变量的初始化:构造参数中MybatisProperties表示是mybatis的一些配置,比如xml和mapper位置,类的别名、类型处理器等等,可以看做是mybatis的配置属性类;ResourceLoader其实质是spring boot的上下文对象,是AnnotationConfigServletWebServerApplicationContext;interceptorsProvider、databaseIdProvider、configurationCustomizersProvider类型实质上都是DefaultListableBeanFactory。
二、创建SqlSessionFactory的bean
接下来会执行@PostConstruct注解的checkConfigFileExists,主要是判断是否需要检查xml,即检查xml是否存在,一般情况下我们不会专门设置这个属性值(默认是false,不检查)。接着会执行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()));
}
Configuration configuration = this.properties.getConfiguration();
if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
configuration = new Configuration();
}
if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
customizer.customize(configuration);
}
}
factory.setConfiguration(configuration);
......
return factory.getObject();
}
这个方法有@ConditionalOnMissingBean注解,即在容器中不存在SqlSessionFactory的时候才会执行,这个方法中首先创建一个SqlSessionFactoryBean对象,然后填充其相关属性如:dataSource、typeAliasesPackage、typeHandlersPackage、mapperLocations等,然后调用SqlSessionFactoryBean的getObject方法,见下一步。
1
getObject方法会判断SqlSessionFactoryBean的成员变量sqlSessionFactory(SqlSessionFactory对象)是否为null,如果不为null,将其返回;否则执行afterPropertiesSet方法,也就是说sqlSessionFactory创建其实是在afterPropertiesSet中完成的,如下:
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
this.sqlSessionFactory = buildSqlSessionFactory();
}
这个方法中主要执行的是buildSqlSessionFactory方法,也就是说这个方法会创建SqlSessionFactory实例,因为代码比较多,所以我删除了一部分代码,保留了部分重要代码,如下:
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
...省略部分代码
Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
..省略部分代码
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
..省略部分代码
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
}
} else {
....省略代码
}
return this.sqlSessionFactoryBuilder.build(configuration);
}
这个方法内容比较多,简单介绍一下,省略的代码主要是设置或者添加Configuration的一些属性值。另外就是注册一些类型别名、类型处理器等注册到Configuration的对于注册容器中,比如TypeHandlerRegistry、TypeAliasRegistry等;另外就是判断是否有事物管理器工厂,如果没有则会创建一个SpringManagedTransactionFactory。然后以environment、transactionFactory、dataSource为构造参数,创建Environment,并设置到Configuration上。可以说前面这部分代码主要还是对Configuration成员变量的一些赋值,或者说填充数据,因为下面要用到Configuration。
接下来会遍历所有的xml文件,每遍历一个xml文件都会创建一个对应的XMLMapperBuilder对象。XMLMapperBuilder以xml文件的InputStream、configuration、xml文件的具体路径、连同configuration的sqlFragments作为构造参数,创建XMLMapperBuilder对象。
看下图XMLMapperBuilder的构造函数:
![](https://img.haomeiwen.com/i14339262/dab2089dfc24a20d.png)
首先执行的是上图中构造方法1,这里面会先以xml的InputStream、configuration的variables变量(是一个Properties对象)、XMLMapperEntityResolver对象等为参数构造创建XPathParser对象。
XPathParser构造函数中先执行commonConstructor方法,完成其对象成员变量初始化,接着执行createDocument方法将xml的InputStream转换成转化成一个Document对象,并将这个Document赋值给XPathParser的成员变量document。然后XPathParser作为参数执行构造方法2。
构造方法2中以configuration为参数执行XMLMapperBuilder的父类构造方法,即BaseBuilder的构造方法,完成BaseBuilder成员变量初始化。接着以configuration、xml文件的具体路径为构造函数创建MapperBuilderAssistant对象,然后将MapperBuilderAssistant对象赋值给XMLMapperBuilder的成员变量builderAssistant。并将XPathParser对象、sqlFragments、resource参数赋值给XMLMapperBuilder的相应的成员变量,完成XMLMapperBuilder成员变量初始化,XMLMapperBuilder初始化结束,执行其parse方法,见下一步。
2
执行XMLMapperBuilder的parse方法。这个方法是核心代码,看下代码:
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
这段代码行数并不多,但是这里面又涉及到了多个方法,我们一行一行分析。
if条件判断的是configuration是否已经加载了当前的xml文件,这里resource是具体的xml路径。如果结果为true,就执行执行2.1,否则执行2.2。
2.1
这里面代码只有3行,第二行configuration.addLoadedResource(resource);
,这行代码就是将当前的xml文件加到configuration的一个属性集合中,表示该xml文件已经读取过了,不需要再次读取,重点是第一行和第三行。
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
2.1.1
先看第一行代码中的parser.evalNode("/mapper")方法,这个执行的是XPathParser的evalNode方法,这个方法其实就是将XPathParser持有的Document对象(xml文件)转成一个XNode对象,这个对象包含了xml文件的的所有内容,可以理解为XNode是一个只有"mapper"标签之间内容的对象如下:
<mapper namespace="com.ypc.mybatis.mapper.UserMapper">
<resultMap id="BaseResultMap" type="com.ypc.mybatis.model.User">
<result column="id" jdbcType="INTEGER" property="id"/>
<result column="userName" jdbcType="VARCHAR" property="userName"/>
<result column="age" jdbcType="INTEGER" property="age"/>
<result column="sex" jdbcType="VARCHAR" property="sex"/>
</resultMap>
<sql id="BaseColumnList">
id, userName, age, sex
</sql>
<!-- 根据主键查询-->
<select id="selectByUserId" parameterType="int" resultType="com.ypc.mybatis.model.User">
select
<include refid="BaseColumnList"></include>
from t_user where id = #{id}
</select>
<!--查询所有用户-->
<select id="findAll" resultType="com.ypc.mybatis.model.User">
select
<include refid="BaseColumnList"></include>
from t_user
</select>
</mapper>
2.1.2
接下来configurationElement方法本身,上面我们知道了configurationElement方法参数是一个XNode,即一个xml的内容。下面具体的看下这个方法的代码,如下:
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);
}
}
这个方法会将XNode(xml文件)进行解析,获取到xml中所有相关属性值。context.getStringAttribute("namespace")获取xml的namespace,并赋值给MapperBuilderAssistant对象;然后是"cache-ref"和"cache",接着是配置的"parameterMap"、"resultMap"、"sql"、"select|insert|update|delete"等,说白了就是将xml的内容全部的读取出来,并进行相应的处理,比如赋值给MapperBuilderAssistant对象,具体的方法不再继续深究,因为内容实在是太多了,我们主要看下buildStatementFromContext,因为这个方法处理的xml中的sql,见下一步。
2.1.2-1
buildStatementFromContext方法,其实根据名称和参数就可以猜到它主要构建sql的。context.evalNodes方法会将xml中的每一条sql语句都转成一个XNode对象,即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);
}
}
}
这段代码中会创建一个XMLStatementBuilder对象,然后执行其parseStatementNode方法,其实核心就是XMLStatementBuilder的parseStatementNode方法,继续向下看:
public void parseStatementNode() {
.....
LanguageDriver langDriver = getLanguageDriver(lang);
......
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
KeyGenerator keyGenerator;
......
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);
}
这个方法代码比较多,上面省略了很多,留下了一点主要的代码。
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
如果不特意指定的话,langDriver实际上是一个XMLLanguageDriver对象,方法具体内容看2.1.1。
另外会判断当前的sql标签是否使用了"keyGenerator"属性,且sql是不是"insert"类型,最终返回相应的keyGenerator对象,Jdbc3KeyGenerator.INSTANCE或者 NoKeyGenerator.INSTANCE。然后将这条sql的所有参数信息添加到MapperBuilderAssistant中。
2.1.2-2
我们主要看一下createSqlSource方法。在XMLLanguageDriver的createSqlSource方法会创建一个XMLScriptBuilder对象,然后执行XMLScriptBuilder对象的parseScriptNode方法,代码如下:
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
public SqlSource parseScriptNode() {
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource = null;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
protected MixedSqlNode parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<SqlNode>();
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents);
isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
parseScriptNode方法内会先执行parseDynamicTags方法,这个方法的主要作用就是完整的解析当前的sql片段,即把select、delete、insert、update这些sql标签的内容完整的读取出来。这个方法使用context(一个XNode对象)作为参数,读取里面的所有string和子标签内容,并将读取到的没一个sql片段变成SqlNode对象,添加到contents中,最后以contentx作为构造函数,创建一个MixedSqlNode对象并返回。比如现在这个XNode对象的id是selectByUserId,xml如下:
<select id="selectByUserId" parameterType="int" resultType="com.ypc.mybatis.model.User">
select
<include refid="BaseColumnList"></include>
from t_user where id = #{id}
</select>
注意一点的是这个XNode并不是整个<select>标签中的内容,它这里是一层层遍历的。通过debug模式就可以看出具体的XNode值以及最后转译的结果,如下图:
![](https://img.haomeiwen.com/i14339262/9a7c9a8ad8f0a0cf.png)
根据上图并结合着上面的xml文件应该更直观的了解到这个方法到底做了哪些工作,其实说白了这个方法才是是真正的将xml的sql标签转成相应的string。接着根据isDynamic属性值,执行不同类型SqlSource的构造函数。看下面的代码:
public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
this(configuration, getSql(configuration, rootSqlNode), parameterType);
}
private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
DynamicContext context = new DynamicContext(configuration, null);
rootSqlNode.apply(context);
return context.getSql();
}
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> clazz = parameterType == null ? Object.class : parameterType;
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());
}
RawSqlSource构造函数中先执行getSql方法,这个方法将遍历SqlNode对象的contents,将contents的sql片段拼接成一条完整的sql语句,这时候sql语句变成了
select id,username,age,sex from t_user where id = #{id}
接着会在构造函数中创建一个SqlSourceBuilder对象,并通过它的parse方法创造需要的SqlSource,代码如下:
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql = parser.parse(originalSql);
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
这个方法会将上面的sql转成如下形式的JDBC的sql语句:
select id,username,age,sex from t_user where id = ?
并且这个方法中会根据sql的参数类型、configuration和一个额外参数类型集合创建一个ParameterMappingTokenHandler对象,这个对象可以获取到所有相关的参数。
方法最后创建一个StaticSqlSource对象,该对象包含了上面的sql语句、Configuration和一个List<ParameterMapping>,即当前这条sql参数的List。最后返回该对象。到这里buildStatementFromContext算是执行完了,相应的configurationElement也就执行结束。
2.1.3
接着我们看bindMapperForNamespace方法。这个方法根据名称应该猜到它的作用就是根据xml的"namespace"和相应的mapper进行绑定,看下面的代码:
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);
}
}
}
}
首先从MapperBuilderAssistant获取到当前"namespace"的值,然后获取到对应的mapper类对象。如果mapper对象没有注册到configuration的mapperRegistry(MapperRegistry对象)属性中,则将其注册到mapperRegistry,为防止spring重复加载,将其添加到已加载的集合中。我们着重看下 configuration.addMapper(boundType)方法,代码如下:
//Configuration类
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
// MapperRegistry类
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);
}
}
}
}
主要是MapperRegistry的addMapper方法,这个方法先判断是不是接口类型,然后判断该接口类对象是否已经注册到MapperRegistry,即是否添加到knownMappers中。没有则以该接口类对象为key,新建的MapperProxyFactory对象(接口类对象为构造参数)为value,添加到knownMappers集合中。接着创建MapperAnnotationBuilder对象,并执行其parse方法,详情看下面3.1部分。
2.1.3-1
MapperAnnotationBuilder根据名称应该是和mapper接口注解的相关的,代码如下:
public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
String resource = type.getName().replace('.', '/') + ".java (best guess)";
this.assistant = new MapperBuilderAssistant(configuration, resource);
this.configuration = configuration;
this.type = type;
sqlAnnotationTypes.add(Select.class);
sqlAnnotationTypes.add(Insert.class);
sqlAnnotationTypes.add(Update.class);
sqlAnnotationTypes.add(Delete.class);
sqlProviderAnnotationTypes.add(SelectProvider.class);
sqlProviderAnnotationTypes.add(InsertProvider.class);
sqlProviderAnnotationTypes.add(UpdateProvider.class);
sqlProviderAnnotationTypes.add(DeleteProvider.class);
}
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
构造方法就不再讲了,主要就是一些初始化的操作,添加一些常用的sql注解。如果configuration还没有加载过resource则执行里面的方法,这个需要注意一点,这时候resource的值是"interface com.ypc.mybatis.mapper.UserMapper",因为type是一个类对象。接着执行loadXmlResource方法,这个方法主要是根据接口类名称判断configuration是否加载过相应的mapper,从而决定要不要去加载相应的xml,因为前面已经添加过了,所以不再需要重新加载xml文件,这个方法其实就是上面2中的讲过的XMLMapperBuilder的parse方法,感兴趣的话可以具体看相应的代码。
接着会获取到接口类的所有public方法,接着对方法数字进行遍历,如果当前遍历的方法不是一个bridge方法,执行parseStatement方法。
2.1.3-2
这个方法其实和2.1中的parseStatementNode比较类似,只不过这个方法操作对象是mapper上的sql注解。因为我代码上没有添加相关sql注解,这里就不在细说了,感兴趣的可以自己好好看下。
2.1.3-3
parse中最后会执行parsePendingMethods方法,这个方法是解析尚未完成的方法,即先从configuration中获取到尚未执行过MapperAnnotationBuilder的parse的方法集合,然后对该集合遍历,对每个方法执行解析,如上面的一样。到这里MapperRegistry的addMapper执行完成,bindMapperForNamespace也就完成了。
bindMapperForNamespace和configurationElement比较相似,区别之处在于bindMapperForNamespace解析的是mapper,而configurationElement解析的是xml,作用我觉得是相同的那就是解析sql。
2.2
代码如下,这部分代码和一部分中的parsePendingMethods方法类似,只是解析对象不同而已,都是从configuration获取相关的集合进行遍历,判断是否需要解析。
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
执行完一部分和二部分的代码,XMLMapperBuilder的parse方法执行完毕。接着需要执行的是SqlSessionFactoryBuilder的build方法:
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
Configuration作为构造参数创建DefaultSqlSessionFactory对象,并返回,至此完成了MybatisAutoConfiguration中sqlSessionFactory方法,即完成了sqlSessionFactory的bean创建完成。
三、结语
本来还想继续往下看的,但是无奈篇幅太长了,而且整体写的内容感觉也很乱,因为方法一层层的,看着还是很累,感觉看源码的方法有问题,看了这么多才只是看到SqlSessionFactory-bean的创建。而后面的内容还有很多,只能下次继续了。SqlSessionFactory-bean的创建过程中做的工作还是非常多的,总结起来我觉得无非有2点:
1、解析xml,解析整个xml文件,主要是sql的解析,将xml中的sql转成JDBC类型sql。
2、解析mapper,主要是解析mapper接口上的sql注解,另外就是创建接口类的MapperProxyFactory对象。
当然实际过程并不像总结的这么简单,实际涉及的点还是很多的,包括很多我都没有深究的地方,不过对整个流程应该是比较清楚了。MybatisAutoConfiguration中还有一个bean,那就是sqlSessionTemplate,这部分代码我还没有仔细去看,下周有时间的话会专门的看这部分代码,最后是项目启动完成后,mapper代理类的执行过程。今天就到这里,有什么疑问欢迎大家相互交流。
网友评论