簡介
MyBatis的前身叫iBatis,本是apache的一個開源項目, 2010年這個項目由apache software foundation 遷移到了google code,並且改名為MyBatis。MyBatis是支持普通SQL查詢,存儲過程和高級映射的優秀持久層框架。MyBatis消除了幾乎所有的JDBC代碼和參數的手工設置以及結果集的檢索。MyBatis使用簡單的XML或注解用於配置和原始映射,將接口和Java的POJOs(Plan Old Java Objects,普通的Java對象)映射成數據庫中的記錄。
實驗代碼
public static void main(String[] args) throws Exception {
SqlSessionFactory sessionFactory = null;
String resource = "configuration.xml";
sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource));
SqlSession sqlSession = sessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
System.out.println(userMapper.findUserById(1));
}
configuration.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配置文件, 我這里面配置的是數據庫相關 -->
<properties resource="resource.properties"></properties>
<!-- 指定Mybatis使用log4j -->
<settings>
<setting name="logImpl" value="LOG4J" />
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<!-- 上面指定了數據庫配置文件, 配置文件里面也是對應的這四個屬性 -->
<property name="driver" value="${jdbc.driverClass}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.userName}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
<!-- 映射文件,mybatis精髓, 后面才會細講 -->
<mappers>
<mapper resource="sqlmap/UserMapper.xml"/>
</mappers>
</configuration>
resource.properties
jdbc.driverClass=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8 jdbc.userName=root jdbc.password=root
SqlSessionFactoryBuilder
更加上面demo的main方法我們先來看下 SqlSessionFactoryBuilder,他的主要目的是創建一個SqlSessionFactory
看下面源碼:
/**
* Builds {@link SqlSession} instances.
*
* @author Clinton Begin
*/
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(Reader reader) { //Reader讀取mybatis配置文件 "configuration.xml";
return build(reader, null, null);
}
public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null);
}
public SqlSessionFactory build(Reader reader, Properties properties) {
return build(reader, null, properties);
}
//通過XMLConfigBuilder解析mybatis配置,然后創建SqlSessionFactory對象
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment) {
return build(inputStream, environment, null);
}
public SqlSessionFactory build(InputStream inputStream, Properties properties) {
return build(inputStream, null, properties);
}
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);
}
}
通過源碼,我們可以看到SqlSessionFactoryBuilder有不同的參數的build,通過XMLConfigBuilder 去解析我們傳入的mybatis的配置文件,轉化成Configuration,通過SqlSessionFactory build(Configuration config)
最終創建DefaultSqlSessionFactory,下面就接着看看 XMLConfigBuilder 部分源碼
// mybatis 配置文件解析
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
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;
}
//讀取XMLConfigBuilder返回一個Configuration對象
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));//xml的根節點
return configuration;
}
//configuration下面能配置的節點為以下11個節點 reflectionFactory是后面版本新增的
//不同子節點進行Element處理
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);
}
}
通過以上源碼,我們就能看出,在mybatis的配置文件中:
1. configuration節點為根節點。
2. 在configuration節點之下,我們可以配置11個子節點, 分別為:properties、typeAliases、plugins、objectFactory、objectWrapperFactory、settings、environments、databaseIdProvider、typeHandlers、mappers、reflectionFactory(后面版本新增的)
如下圖:

下面我們看下子節點的源碼
1:properties(相關配置讀取)
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) { //如果resource和url都為空就拋出異常
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);
}
//把Properties defaults 設置到 parser.setVariables
parser.setVariables(defaults);
//把Properties defaults 設置到 configuration.setVariables
configuration.setVariables(defaults);
}
}
2:settings 全局性的配置
private Properties settingsAsPropertiess(XNode context) {
if (context == null) {
return new Properties();
}
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
for (Object key : props.keySet()) {
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
return props;
}
//settings元素設置 元素比較多不懂的可以看下官方文檔
private void settingsElement(Properties props) throws Exception {
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true));
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setLogImpl(resolveClass(props.getProperty("logImpl"))); //這個是我們的配置文件里面設置的 日志
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}
有關全局設置說明

3:typeAliases 為一些類定義別名
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class clazz = Resources.classForName(type);
if (alias == null) {
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
public void registerAlias(String alias, Class value) {
if (alias == null) {
throw new TypeException("The parameter alias cannot be null");
}
// issue #748
String key = alias.toLowerCase(Locale.ENGLISH);
if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
}
TYPE_ALIASES.put(key, value); //最終是放到map里面
}
4:environments Mybatis的環境
//數據源 事務管理器 相關配置
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default"); //默認值是default
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
// MyBatis 有兩種事務管理類型(即type=”[JDBC|MANAGED]”) Configuration.java的構造函數
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));//事物
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); //數據源
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build()); //放到configuration里面
}
}
}
}
5: mappers 映射文件或映射類
既然 MyBatis 的行為已經由上述元素配置完了,我們現在就要定義 SQL 映射語句了。 但是, 首先我們需要告訴 MyBatis 到哪里去找到這些語句。 Java 在這方面沒有提供一個很好 的方法, 所以最佳的方式是告訴 MyBatis 到哪里去找映射文件。 你可以使用相對於類路徑的 資源引用,或者字符表示,或 url 引用的完全限定名(包括 file:///URLs)
/
/讀取配置文件將接口放到configuration.addMappers(mapperPackage)
//四種方式 resource、class name url
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {//demo是通過xml加載的
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
以上是幾個常用的配置文件元素源碼分析,其它的不在這里過多介紹,因為有些子元素本人也沒有用到過。
