一方面是增强自己对这个现在非常流行的持久层框架理解,一方面是最近想做一个类似于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标签,判断是否加载外部配置文件
this.propertiesElement(root.evalNode("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")));
}