SqlSessionFactoryBuilder是構建sqlSessionFactory的入口類

從該類的方法可知,它是通過不同的入參來構造SqlSessionFactory,除了最后一個configuration入參方法外,其余方法最終都調用如下方法
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) {
// Intentionally ignore. Prefer previous error.
}
}
}
如上圖所示,先創建了一個XMLConfigBuilder類的實例
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
在這個初始化過程中,首先創建了XMLMapperEntityResolver類的實例,這個類顧名思義是個實體mapper文件實體解析器,里面有個map將mybatis的xml文件與對應的解析文件關系保存起來,初始化后再實例化XPathParser
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(inputStream));
}
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
commonConstructor比較簡單,就是給一些類變量賦值,並且初始化了一個xpath對象,這里使用了工廠設計模式。
new InputSource(inputStream)就是將輸入流賦予自己內部的一個變量byteStream。
createDocument(new InputSource(inputStream))這里面主要做了這幾件事通過DocumentBuilderFactory生成DocumentBuilder,並將entityResolver賦給它的屬性字段同時定義了一個處理異常的內部類,
然后通過builder.parse(inputSource)將輸入流解析成一個document對象。
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
new Configuration()主要是在構造方法里面注冊了別名
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
registerAlias是TypeAliasRegistry類的方法,將別名與類的關系保存在了內部的private final Map<String, Class<?>> TYPE_ALIASES
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
super(new Configuration())就是將相應的變量保存在父類屬性上,方便其它子類使用。
ErrorContext.instance()方法內部采用的threadlocal方式,每個線程都持有一個ErrorContext對象resource("SQL Mapper Configuration")就是一個簡單的賦值給resource變量方法。
至此一個XMLConfigBuilder對象就創建出來了,然后就進入了build(parser.parse())。
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
parser.evalNode("/configuration")在初始化過程中已經將mybatis_config.xml文件解析成document對象,這里使用xpath解析工具將configuration節點解析成一個XNode對象。
private void parseConfiguration(XNode root) {
try {
Properties settings = settingsAsPropertiess(root.evalNode("settings"));
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectionFactoryElement(root.evalNode("reflectionFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
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);
}
}
該方法一看便知是掃描configuration節點下的諸如properties、typeAliases節點做相應的處理。稍微分析其中幾個方法
propertiesElement(root.evalNode("properties"))是讀取properties節點下的property子元素以及resource指向的資源文件路徑,將兩者合並為一個properties並保存到內部的vars變量。
typeAliasesElement(root.evalNode("typeAliases"))讀取子元素時首先判斷是否package,是的話掃描包下的非接口、匿名、內部類注冊別名。
pluginElement(root.evalNode("plugins"))將插件加到插件鏈上。
重點提一下最后的mapperElement方法,這個方法內首先判斷子元素是不是package,若是<mapper resource="mapper/demo.xml"/>這種形式的。其中最重要的是下面兩行代碼
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
構建XMLMapperBuilder對象前面已經分析過了,重點分析parse方法
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}
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);
}
}
}
}
該方法獲取mapper文件的namespace來實例化mapper接口,這也就解釋了為什么namespace要和mapper接口全路徑一致。
其中值得注意的是configuration.addMapper(boundType)方法,該方法調用了mapperRegistry.addMapper(type)方法。
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);
}
}
}
}
MapperProxyFactory是個代理接口方法工廠,后面會使用到它,就是通過它來給沒有實現類的mapper接口代理。
重點看其中的parser.parse()方法
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();
}
先得到接口里的methods,然后遍歷解析方法,查看該方法有沒有注釋sql語句有的話拼接這些語句,因為現在流行使用xml文件配置方式更加靈活的處理sql語句,因此這里跳過。
parsePendingMethods方法是有些insert或者update的select語句引入了sql片段,但是sql片段還沒解析到,因此先將這些方法pend,后面每有新的mapper解析時都會嘗試解析完這些pend方法。
同理parsePendingResultMaps(),parsePendingChacheRefs(),parsePendingStatements()三個方法也類似。
現在configuration已經基本解析完成,然后調用
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
返回一個默認的SqlSessionFactory實現,其內部持有一個Configuration變量。
至此SqlSessionFactoryBuilder創建DefaultSqlSessionFactory的過程完成。