一方面是增強自己對這個現在非常流行的持久層架構了解,一方面是最近想做一個類似于PageHelper的分頁插件,是以對mybatis的具體工作原理還是要有一定的認識,才能完成這個插件。
核心類:
- SqlSessionFactoryBuilder 用于生産SqlSessionFactory
- XMLConfigBuilder 用于解析mybatis的核心配置檔案sqlMapConfig.xml
先看一下mybatis核心配置檔案sqlMapConfig.xml
<?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>
<!--加載配置屬性-->
<properties resource="db.properties"></properties>
<settings>
<!-- 打開延遲加載的開關 -->
<setting name="lazyLoadingEnabled" value="false" />
<!-- 将積極加載改為消息加載即按需加載 -->
<setting name="aggressiveLazyLoading" value="false" />
<setting name="lazyLoadTriggerMethods" value=""/>
<setting name="cacheEnabled" value="true" />
<setting name= "mapUnderscoreToCamelCase" value = "true"/>
<setting name="logImpl" value="STDOUT_LOGGING" />
<setting name="useGeneratedKeys" value="true" />
</settings>
<typeAliases>
<package name="top.takefly.pojo"/>
</typeAliases>
<!--配租插件-->
<plugins>
<plugin interceptor="com.github.pagehelper.PageHelper">
<property name="dialect" value="mysql"/>
</plugin>
</plugins>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED" >
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
<environment id="oracle">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
</dataSource>
</environment>
</environments>
<mappers>
<!-- <mapper class="top.takefly.dao.IUserDao"/>-->
<!-- <mapper resource="top/takefly/dao/IUserDao.xml" />-->
<package name="top.takefly.dao"/>
</mappers>
</configuration>
第一步
首先要通過SqlSessionBuilder建構出SqlSessionFactory,這是通過工廠模式建構出來的。
工廠模式的好處:
1.将對象的建立交由工廠來維護,隻需要從工廠擷取對象即可。
2.易于功能擴充
3.遵循開閉原則
4.封裝性,對外提供接口,具體實作由工廠來選擇
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
我們來看一下mybatis是如何傳入Configuration對象的
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException var13) {
}
}
return var5;
}
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
SqlSessionFactory var5 = this.build(parser.parse());
從代碼中我們看到了,Configuration對象是由XMLConfigBuilder的parse方法解析出來的,parser.parse()傳回的是一個Configuration對象,this.build(Configuration config)調用的參數為Configuration的重載構造。
現在具體看一下XMLConfigBuilder是如何通過parse()方法解析出Configuration的
public Configuration parse() {
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}
首先XMLConfigBuilder的parse()會判斷目前XMLConfigBuilder是否被使用過,如果被使用過就會抛出異常,如果沒有使用過就會将parsed辨別為true,同時調用parseConfiguration(XNode root)方法,這個this.parser.evalNode("/configuration")擷取的就是configuration标簽下的所有标簽,接下來看看parseConfiguration(XNode root)方法是如何解析的
private void parseConfiguration(XNode root) {
try {
this.propertiesElement(root.evalNode("properties"));
Properties settings = this.settingsAsProperties(root.evalNode("settings"));
this.loadCustomVfs(settings);
this.loadCustomLogImpl(settings);
this.typeAliasesElement(root.evalNode("typeAliases"));
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
this.settingsElement(settings);
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlerElement(root.evalNode("typeHandlers"));
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
解析步驟如下
- 擷取properties标簽,判斷是否加載外部配置檔案
- 擷取setting标簽中的配置,擷取Settings标簽中配置的屬性
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
} else {
Properties props = context.getChildrenAsProperties();
MetaClass metaConfig = MetaClass.forClass(Configuration.class, this.localReflectorFactory);
Iterator var4 = props.keySet().iterator();
Object key;
do {
if (!var4.hasNext()) {
return props;
}
key = var4.next();
} while(metaConfig.hasSetter(String.valueOf(key)));
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
3.加載自定義元件,其實就是添加一個Interceptor
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
Iterator var2 = parent.getChildren().iterator();
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor)this.resolveClass(interceptor).newInstance();
interceptorInstance.setProperties(properties);
this.configuration.addInterceptor(interceptorInstance);
}
}
}
- 設定settingsElement
private void settingsElement(Properties props) {
this.configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
this.configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
this.configuration.setCacheEnabled(this.booleanValueOf(props.getProperty("cacheEnabled"), true));
this.configuration.setProxyFactory((ProxyFactory)this.createInstance(props.getProperty("proxyFactory")));
this.configuration.setLazyLoadingEnabled(this.booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
this.configuration.setAggressiveLazyLoading(this.booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
this.configuration.setMultipleResultSetsEnabled(this.booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
this.configuration.setUseColumnLabel(this.booleanValueOf(props.getProperty("useColumnLabel"), true));
this.configuration.setUseGeneratedKeys(this.booleanValueOf(props.getProperty("useGeneratedKeys"), false));
this.configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
this.configuration.setDefaultStatementTimeout(this.integerValueOf(props.getProperty("defaultStatementTimeout"), (Integer)null));
this.configuration.setDefaultFetchSize(this.integerValueOf(props.getProperty("defaultFetchSize"), (Integer)null));
this.configuration.setDefaultResultSetType(this.resolveResultSetType(props.getProperty("defaultResultSetType")));
this.configuration.setMapUnderscoreToCamelCase(this.booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
this.configuration.setSafeRowBoundsEnabled(this.booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
this.configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
this.configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
this.configuration.setLazyLoadTriggerMethods(this.stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
this.configuration.setSafeResultHandlerEnabled(this.booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
this.configuration.setDefaultScriptingLanguage(this.resolveClass(props.getProperty("defaultScriptingLanguage")));
this.configuration.setDefaultEnumTypeHandler(this.resolveClass(props.getProperty("defaultEnumTypeHandler")));
this.configuration.setCallSettersOnNulls(this.booleanValueOf(props.getProperty("callSettersOnNulls"), false));
this.configuration.setUseActualParamName(this.booleanValueOf(props.getProperty("useActualParamName"), true));
this.configuration.setReturnInstanceForEmptyRow(this.booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
this.configuration.setLogPrefix(props.getProperty("logPrefix"));
this.configuration.setConfigurationFactory(this.resolveClass(props.getProperty("configurationFactory")));
}