Mybatis原始碼解析之Mybatis初始化過程
一、搭建一個簡單的Mybatis工程
為了瞭解Mybatis的初始化過程,這裡需要搭建一個簡單的Mybatis工程操作資料庫,工程結構如下: 一個UserBean.java
private int id;
private String username;
private String password;
private int age;
public UserBean(String username, String password, int age) {
super();
this.username = username;
this .password = password;
this.age = age;
}
操作資料庫介面以及對應的Mapper
public interface UserMapper {
void insertUser(UserBean user);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zjp.mapper.UserMapper">
<insert id="insertUser" parameterType="userBean">
insert into user_t (id,user_name,password,age) values(#{id},#{username},#{password},#{age})
</insert>
</mapper>
mybatis配置檔案mybatis-config以及jdbc配置檔案
<?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="jdbc.properties">
<property name="dialect" value="mysql" />
</properties>
<settings>
<setting name="logImpl" value="LOG4J" />
</settings>
<typeAliases>
<typeAlias type="com.zjp.bean.UserBean" alias="userBean" />
</typeAliases>
<!-- 配置mybatis執行環境 -->
<environments default="cybatis">
<environment id="cybatis">
<!-- type="JDBC" 代表使用JDBC的提交和回滾來管理事務 -->
<transactionManager type="JDBC" />
<!-- mybatis提供了3種資料來源型別,分別是:POOLED,UNPOOLED,JNDI -->
<!-- POOLED 表示支援JDBC資料來源連線池 -->
<!-- UNPOOLED 表示不支援資料來源連線池 -->
<!-- JNDI 表示支援外部資料來源連線池 -->
<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>
<!-- 對映檔案方式1,一個一個的配置-->
<mapper resource="com/zjp/mapper/UserBeanMapper.xml" />
</mappers>
</configuration>
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8
username=root
password=root
#\u5B9A\u4E49\u521D\u59CB\u8FDE\u63A5\u6570
initialSize=0
#\u5B9A\u4E49\u6700\u5927\u8FDE\u63A5\u6570
maxActive=20
#\u5B9A\u4E49\u6700\u5927\u7A7A\u95F2
maxIdle=20
#\u5B9A\u4E49\u6700\u5C0F\u7A7A\u95F2
minIdle=1
#\u5B9A\u4E49\u6700\u957F\u7B49\u5F85\u65F6\u95F4
maxWait=60000
這樣就完成了一個簡單的mybatis工程搭建,然後新建一個main方法對資料庫進行操作
package com.zjp;
import java.io.IOException;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import com.zjp.bean.UserBean;
import com.zjp.mapper.UserMapper;
public class Test {
public static void main(String[] args) throws IOException {
// 使用MyBatis提供的Resources類載入mybatis的配置檔案
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
// 構建sqlSession的工廠
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
UserBean user = new UserBean("張三", "123456", 7);
try {
mapper.insertUser(user);
System.out.println(user.toString());
session.commit();
} catch (Exception e) {
e.printStackTrace();
session.rollback();
}
}
}
二、Mybatis配置檔案初始化過程
在Main方法中前面兩行程式碼就是配置檔案的初始化過程
1、建立SqlSessionFactoryBuilder物件
// 使用MyBatis提供的Resources類載入mybatis的配置檔案
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
// 構建sqlSession的工廠
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
第一步是獲取配置檔案的reader,然後獲取SqlSessionFactory ,SqlSessionFactory是MyBatis的關鍵物件,它是個單個數據庫對映關係經過編譯後的記憶體映象通過原始碼可知SqlSessionFactory 是介面,
public interface SqlSessionFactory {
//8個方法可以用來建立SqlSession例項
SqlSession openSession();
//自動提交
SqlSession openSession(boolean autoCommit);
//連線
SqlSession openSession(Connection connection);
//事務隔離級別
SqlSession openSession(TransactionIsolationLevel level);
//執行器的型別
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);
Configuration getConfiguration();
}
SqlSessionFactoryBuilder就是建立,通過build方法對配置檔案進行解析並初始化,通過原始碼可知build方法有很多過載, 通過原始碼可知,所有的build方法最後都是通過
public SqlSessionFactory build(Reader reader, String environment, Properties properties)
這方法來執行,所以SqlSessionFactoryBuilder的功能就是對輸入的配置檔案流進行解析最後生成SqlSessionFactory 。
2 建立SqlSessionFactory
進入最終都需要進入的build方法
/**
* 第4種方法是最常用的,它使用了一個參照了XML文件或更特定的SqlMapConfig.xml檔案的Reader例項。
* 可選的引數是environment和properties。Environment決定載入哪種環境(開發環境/生產環境),包括資料來源和事務管理器。
* 如果使用properties,那麼就會載入那些properties(屬性配置檔案),那些屬性可以用${propName}語法形式多次用在配置檔案中
*/
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
// 委託XMLConfigBuilder來解析xml檔案,並構建
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
// 這裡是捕獲異常,包裝成自己的異常並丟擲的idiom?,最後還要reset ErrorContext
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
build函式首先會構造一個XMLConfigBuilder物件,從名字上可以看出來,該物件是用來解析XML配置檔案的。通過XMLConfigBuilder將配置資訊轉換成paser
//建構函式,轉換成XPathParser再去呼叫建構函式
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
//構造一個需要驗證,XMLMapperEntityResolver的XPathParser
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
//上面6個建構函式最後都合流到這個函式,傳入XPathParser
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
//首先呼叫父類初始化Configuration
super(new Configuration());
//錯誤上下文設定成SQL Mapper Configuration(XML檔案配置),以便後面出錯了報錯用吧
ErrorContext.instance().resource("SQL Mapper Configuration");
//將Properties全部設定到Configuration裡面去
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
在這裡有一個很重要的類Configuration,最後所有的配置資訊都會封裝到這個類裡面,這個裡面擁有很多引數
//環境
protected Environment environment;
//---------以下都是<settings>節點-------
protected boolean safeRowBoundsEnabled = false;
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase = false;
protected boolean aggressiveLazyLoading = true;
protected boolean multipleResultSetsEnabled = true;
protected boolean useGeneratedKeys = false;
protected boolean useColumnLabel = true;
//預設啟用快取
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls = false;
protected String logPrefix;
protected Class <? extends Log> logImpl;
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
protected Integer defaultStatementTimeout;
//預設為簡單執行器
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
//---------以上都是<settings>節點-------
protected Properties variables = new Properties();
//物件工廠和物件包裝器工廠
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
//對映註冊機
protected MapperRegistry mapperRegistry = new MapperRegistry(this);
//預設禁用延遲載入
protected boolean lazyLoadingEnabled = false;
protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
protected String databaseId;
/**
* Configuration factory class.
* Used to create Configuration for loading deserialized unread properties.
*
* @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300</a> (google code)
*/
protected Class<?> configurationFactory;
protected final InterceptorChain interceptorChain = new InterceptorChain();
//型別處理器註冊機
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
//類型別名註冊機
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
//對映的語句,存在Map裡
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
//快取,存在Map裡
protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
//結果對映,存在Map裡
protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");
protected final Set<String> loadedResources = new HashSet<String>();
protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
//不完整的SQL語句
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();
/*
* A map holds cache-ref relationship. The key is the namespace that
* references a cache bound to another namespace and the value is the
* namespace which the actual cache is bound to.
*/
protected final Map<String, String> cacheRefMap = new HashMap<String, String>();
public Configuration(Environment environment) {
this();
this.environment = environment;
}
3、解析配置檔案
獲取到了XMLConfigBuilder之後就可以對配置進行解析了
//解析配置
public Configuration parse() {
//如果已經解析過了,報錯
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//根節點是configuration
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
解析配置檔案的重要邏輯
//解析配置
private void parseConfiguration(XNode root) {
try {
//分步驟解析
//issue #117 read properties first
//1.properties
propertiesElement(root.evalNode("properties"));
//2.類型別名
typeAliasesElement(root.evalNode("typeAliases"));
//3.外掛
pluginElement(root.evalNode("plugins"));
//4.物件工廠
objectFactoryElement(root.evalNode("objectFactory"));
//5.物件包裝工廠
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//6.設定
settingsElement(root.evalNode("settings"));
// read it after objectFactory and objectWrapperFactory issue #631
//7.環境
environmentsElement(root.evalNode("environments"));
//8.databaseIdProvider
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//9.型別處理器
typeHandlerElement(root.evalNode("typeHandlers"));
//10.對映器
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
通過上面的程式碼就可以明確的看出來,mybatis對配置檔案的每個不同節點的解析過程,由於解析的節點太多,平時在開發接觸較多的properties、typeAliases、environments、mappers
3.1properties的解析
假如有一個這樣的節點需要解析
<properties resource="jdbc.properties">
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>
解析過程
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
//傳入方式是呼叫建構函式時傳入,public XMLConfigBuilder(Reader reader, String environment, Properties props)
//1.XNode.getChildrenAsProperties函式方便得到孩子所有Properties
Properties defaults = context.getChildrenAsProperties();
//2.然後查詢resource或者url,加入前面的Properties
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
// resource和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) {
// 將所有的配置資訊載入到Properties中
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
//3.Variables也全部加入Properties
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
- 獲取到下面的所有子節點資訊並將這些資訊放到Properties 中;
- 然後讀取resource或者url中的配置加入到Properties 中;
最後將所有的配置資訊儲存到configuration中
如果在這些地方,屬性多於一個的話,MyBatis 按照如下的順序載入它們: 1.在 properties 元素體內指定的屬性首先被讀取。 2.從類路徑下資源或 properties 元素的 url 屬性中載入的屬性第二被讀取,它會覆蓋已經存在的完全一樣的屬性。 3.作為方法引數傳遞的屬性最後被讀取, 它也會覆蓋任一已經存在的完全一樣的屬性,這些屬性可能是從 properties 元素體內和資源/url 屬性中載入的。
3.2 typeAliases的解析
別名的解析
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
//如果是package
String typeAliasPackage = child.getStringAttribute("name");
//(一)呼叫TypeAliasRegistry.registerAliases,去包下找所有類,然後註冊別名(有@Alias註解則用,沒有則取類的simpleName)
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
//如果是typeAlias
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
//根據Class名字來註冊類型別名
//(二)呼叫TypeAliasRegistry.registerAlias
if (alias == null) {
//alias可以省略
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
通過上面的程式碼可知,別名的配置方式有package和typeAlias兩種。
<typeAliases>
<package name="com.zjp.bean"/>
</typeAliases>
<typeAliases>
<typeAlias type="com.zjp.bean.UserBean" alias="userBean" />
</typeAliases>
通過上面的兩種方式之後就會為所有的類起一個別名,為類首字母小寫。 最終這些別名都會註冊到configuration的typeAliasRegistry中。
3.3 setting的解析
<settings>
<setting name="logImpl" value="LOG4J" />
</settings>
通過原始碼可知,這個解析較為簡單,就是將讀取到的設定資訊設定到configuration中
//顯式定義用什麼log框架,不定義則用預設的自動發現jar包機制
configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
3.4 mappers的解析
mappers節點解析是mybatis中比較重要,通過原始碼可以知道定義方式有如下四種:
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
//10.4自動掃描包下所有對映器
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) {
//10.1使用類路徑
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
//對映器比較複雜,呼叫XMLMapperBuilder
//注意在for迴圈裡每個mapper都重新new一個XMLMapperBuilder,來解析
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
//10.2使用絕對url路徑
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
//對映器比較複雜,呼叫XMLMapperBuilder
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
//10.3使用java類名
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.");
}
}
}
}
}
通過上面的原始碼可以得出這四種分別是package、resource、url和class。 進入Mapper的解析首先會遍歷所有的子節點,然後判斷不同的方式對Mapper進行解析,解析之後的資訊也是儲存到configuration中。 其中package的解析方式較為簡單,其他的解析方式會使用到一個對映器進行解析
//對映器比較複雜,呼叫XMLMapperBuilder
//注意在for迴圈裡每個mapper都重新new一個XMLMapperBuilder,來解析
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
首先在解析之前會建立一個XMLMapperBuilder ,獲取到之後再進行解析
//解析
public void parse() {
//如果mapper檔案沒有載入過再載入,防止重複載入
if (!configuration.isResourceLoaded(resource)) {
//配置mapper
configurationElement(parser.evalNode("/mapper"));
//標記一下,已經載入過了
configuration.addLoadedResource(resource);
//繫結對映器到namespace
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}
解析mapper
private void configurationElement(XNode context) {
try {
//1.配置namespace
String namespace = context.getStringAttribute("namespace");
if (namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
//2.配置cache-ref
cacheRefElement(context.evalNode("cache-ref"));
//3.配置cache
cacheElement(context.evalNode("cache"));
//4.配置parameterMap(已經廢棄,老式風格的引數對映)
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//5.配置resultMap(高階功能)
resultMapElements(context.evalNodes("/mapper/resultMap"));
//6.配置sql(定義可重用的 SQL 程式碼段)
sqlElement(context.evalNodes("/mapper/sql"));
//7.配置select|insert|update|delete TODO
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
resultMapElements函式 該函式用於解析對映檔案中所有的節點,這些節點會被解析成ResultMap物件,儲存在Configuration物件的resultMaps容器中。
resultMap解析過程:
//5.1 配置resultMap
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
//錯誤上下文
//取得標示符 ("resultMap[userResultMap]")
// <resultMap id="userResultMap" type="User">
// <id property="id" column="user_id" />
// <result property="username" column="username"/>
// <result property="password" column="password"/>
// </resultMap>
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
//一般拿type就可以了,後面3個難道是相容老的程式碼?
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
//高階功能,還支援繼承?
// <resultMap id="carResult" type="Car" extends="vehicleResult">
// <result property="doorCount" column="door_count" />
// </resultMap>
String extend = resultMapNode.getStringAttribute("extends");
//autoMapping
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
resultMappings.addAll(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
//解析result map的constructor
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
//解析result map的discriminator
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List<ResultFlag> flags = new ArrayList<ResultFlag>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
//調5.1.1 buildResultMappingFromContext,得到ResultMapping
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
//最後再調ResultMapResolver得到ResultMap
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
解析完成之後ResultMapResolver將resultMap封裝到configuration中,完成解析。 最後buildStatementFromContext構建sql語句
//7.1構建語句
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
//構建所有語句,一個mapper下可以有很多select
//語句比較複雜,核心都在這裡面,所以呼叫XMLStatementBuilder
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//核心XMLStatementBuilder.parseStatementNode
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
//如果出現SQL語句不完整,把它記下來,塞到configuration去
configuration.addIncompleteStatement(statementParser);
}
}
}
最後將sql語句封裝到configuration中。
4 SQLSessionFactory物件的建立
通過上面的一些列解析以及封裝,把mybatis的配置資訊全部都封裝到configuration中了,所以mybatis中configuration是一個很重要的類
// 最後一個build方法使用了一個Configuration作為引數,並返回DefaultSqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
最終就獲取到了SqlSessionFactory了,以上就是mybatis的初始化過程。