資源加載
mybatis資源加載的過程主要是處理xml里面的三種類型的標簽<configuration><mapper><select|insert|update|delete>這三種標簽分別對應三個處理類:XMLConfigurationBuilder急前;XMLMapperBuilder咏花;XMLStatementBuilder蹄衷。這三個類解析完數(shù)據(jù)之后會將所有的數(shù)據(jù)都放到configuration對象中塘幅。方便后面getMapper皿伺,和執(zhí)行sql使用雁芙。在解析過程中也會將各種標簽肥照,屬性創(chuàng)建合理的對象來與之對應起來鸠信。
- Properties 在configuration對象中保存配置文件中<properties>的值。在propertiesElement這個方法中被解析
- TypeHandlerRegistry 類型處理器注冊的類攒岛,用來處理查詢到的結果中jdbc類型和java類型不匹配進行映射赖临。默認情況下Mybatis已經(jīng)提供了許多類型匹配處理器
- TypeAliasRegistry 別名處理注冊的類,用來收集別名與class對應的映射灾锯,默認Mybatis已經(jīng)提供了許多類型映射例如list表示java.util.List
處理configuration標簽
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
看一下SqlSessionFactoryBuilder類代碼兢榨,可以知道沒有構造方法直接采用默認的構造方法,然后使用build方法創(chuàng)建SqlSessionFactory對象顺饮。這個類采用了build模式吵聪,仿照了spring里面實例ClassPathXmlApplicationContext類的方式。多個方法最終都是調(diào)用一個方法類實例兼雄,如果沒有傳參吟逝,則采用默認方式。
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.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
先看創(chuàng)建XMLConfigBuilder對象的過程
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
//XMLConfigBuilder對象持有XPathParser對象
//XPathParser對象持有Document對象
//Document對象持有inputStream對象解析出來的xml文檔的完整數(shù)據(jù)赦肋。解析方式采用JDK中的javax.xml.parsers
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
繼續(xù)看一下實例化XPathParser對象
//分析xml
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
//這個創(chuàng)建對象澎办,比較簡單,就是屬性的賦值
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(inputStream));
}
以上實現(xiàn)方式采用的是java提供的xml解析方式金砍,在javax.xml.parser這個包里面局蚀。目前通過上面的操作xml文件已經(jīng)變成了Document對象∷〕恚可以通個getElementByName等等方法獲得Element元素琅绅。并且這個Document對象已經(jīng)間接的被XMLConfigBuilder持有。繼續(xù)調(diào)用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;
}
先看上面的parser.evalNode("/configuration")
我們在配置Mybatis的時候可以看到根節(jié)點就是configuration千扶。可以猜測應該是拿到configuration這個節(jié)點的所有數(shù)據(jù)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
加載configuration細節(jié)
繼續(xù)看這句parseConfiguration(parser.evalNode("/configuration"));
這個方法里面是要處理configuration節(jié)點骆捧。在parser.evalNode("/configuration")
里面已經(jīng)拿到了<configuration>節(jié)點澎羞,這個節(jié)點被封裝成了XNode對象。XNode對象是將Node對象作為屬性(設計模式的原則:組合優(yōu)于繼承)在對象中使用敛苇,這個XNode就是獲得<configuration>節(jié)點中的各種數(shù)據(jù)妆绞。然后通過paserConfiguration方法處理這個節(jié)點。其實就是注冊各種信息枫攀±ㄈ模看源代碼
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
//獲得properties節(jié)點,保存到configuration.variables屬性里面
propertiesElement(root.evalNode("properties"));
//將別名添加到configuration.TypeAliasRegistry 這個里面已經(jīng)默認注冊了常用的數(shù)據(jù)類
/**
* 下面簡單列幾個mybatis提供的默認類型別名来涨。當然也可以自定義图焰。會解析下面標簽
* <typeAlias alias="Author" type="org.apache.ibatis.domain.blog.Author"/>
* registerAlias("byte", Byte.class);
* registerAlias("byte[]", Byte[].class);
* registerAlias("_byte", byte.class);
* registerAlias("ResultSet", ResultSet.class);
* registerAlias("list", List.class);
*/
typeAliasesElement(root.evalNode("typeAliases"));
//這個是解析插件,暫時跳過
pluginElement(root.evalNode("plugins"));
//這個也沒怎么用到蹦掐,先跳過
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectionFactoryElement(root.evalNode("reflectionFactory"));
settingsElement(root.evalNode("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);
}
}
加載properties節(jié)點
上面只標注了一些經(jīng)常使用的節(jié)點解析過程技羔,一些不常用的就沒有分析僵闯,例如objectFactory,objectWrapperFactory就不分析了藤滥。先來看一下properties這種鍵值對解析過程鳖粟。過程很簡單分為兩種情況:
- 直接是鍵值對存在的直接生成Properties對象
- 使用Resource進行引入文件的,先讀取文件變?yōu)閕nputStream流超陆,然后從中讀出鍵值對
最后解析完數(shù)據(jù)牺弹,將Properties對象保存到configuration對象中configuration.setVariables(defaults);
//將配置文件里面的properties解析出來浦马,保存到configuration對象里面的variables里面
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
//直接解析類似:<property name="a" value="a1"/>種的鍵值對
Properties defaults = context.getChildrenAsProperties();
//處理Resource這種資源:<properties resource="org/apache/ibatis/databases/blog/blog-derby.properties"/>
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
加載typeAlias節(jié)點
繼續(xù)分析第二個方法typeAliasesElement(root.evalNode("typeAliases"));
這個方法是處理別名时呀。當時用<typeAlias alias="Author" type="org.apache.ibatis.domain.blog.Author"/>
的時候就是在時用別名。這些別名回注冊到configuration對象里面TypeAliasRegistry
對象晶默。這個對象里面已經(jīng)默認創(chuàng)建了一些常用類型的別名谨娜,這就是為什么在寫sql的時候可以直接使用resultType="list"
因為這個類型已經(jīng)默認注冊到TypeAliasRegistry
而在執(zhí)行sql之后返回類型會先到configuration對象里面找到TypeAliasRegistry
里面是否有這個別名的對象,如果沒有就只用反射Class創(chuàng)建對象磺陡。下面簡單看看TypeAliasRegistry
趴梢。所有的類型最終都是放在map里面
public class TypeAliasRegistry {
private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
//省略...
}
}
加載typeHandler節(jié)點
注冊的過程比較簡單就是將name作為可以,class作為值放到map對象里面币他。細節(jié)就不介紹了坞靶。跳過不常用的屬性objectFactory,objectWrapperFactory蝴悉,reflectionFactory彰阴,可以簡單看一下setting。在使用mybatis的時候只要有許多默認的setting配置拍冠。這些配置就是從settingsElement(root.evalNode("settings"));
里面注入進去的尿这。如果沒有設置那么統(tǒng)統(tǒng)都讀取默認配置,設置了就用你自己的庆杜。再來看一下類型處理器TypeHandler這個是指查詢sql之后返回的jdbs類型和java類型不匹配時可以使用你自定義的類型來處理返回數(shù)據(jù)射众。Mybatis里面已經(jīng)設置了好多默認的類型處理器。
/**
* 處理這種類型標簽晃财。分別將javaType,jdbcType,handler三個屬性注冊到configuration.typeHandlerRegistry里面
* <typeHandler javaType="String" jdbcType="VARCHAR" handler="org.apache.ibatis.builder.ExampleTypeHandler"/>
* @param parent
* @throws Exception
*/
private void typeHandlerElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeHandlerPackage = child.getStringAttribute("name");
typeHandlerRegistry.register(typeHandlerPackage);
} else {
String javaTypeName = child.getStringAttribute("javaType");
String jdbcTypeName = child.getStringAttribute("jdbcType");
String handlerTypeName = child.getStringAttribute("handler");
Class<?> javaTypeClass = resolveClass(javaTypeName);
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
Class<?> typeHandlerClass = resolveClass(handlerTypeName);
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
上面的注冊過程很簡單叨橱,就是讀取node下面的jdbcType和javaType和處理類handler。然后注冊到typeHandlerRegistry里面断盛〕猓看一下默認的類型處理器
public TypeHandlerRegistry() {
register(Boolean.class, new BooleanTypeHandler());
register(boolean.class, new BooleanTypeHandler());
register(JdbcType.BOOLEAN, new BooleanTypeHandler());
register(JdbcType.BIT, new BooleanTypeHandler());
//省略.....
}