SqlSessionFactoryBuilder--->XMLConfigBuilder---> XMLMapperBuilder---(橋樑)MapperBuilderAssistant--->XMLStatementBuilder(parseStatementNode方法)--(橋樑)MapperBuilderAssistant(addMappedStatement方法)-->MappedStatement.Builder--->MappedStatement--->confuguration.addMappedStatement()
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
//這一步,根據setting解析的結果,設置我們的一些參數,如果沒有設置則給默認值。
//我們的配置如下:
/**
<settings>
<setting name="cacheEnabled" value="false"/>
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
*/
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
//這一步,解析Mapper.xml
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
/**
<mappers>
<mapper resource="userMapper.xml"/>
</mappers>
*/
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {//子節點 是<package>這樣的
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {//我們在這個分支 ,先獲取 resource,url,class這三個屬性的值
//我們的resource值爲:userMapper.xml
//這三個屬性值,只能三選一。
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
//我們在這個分支
ErrorContext.instance().resource(resource);
//讀取我們的userMapper.xml文件,轉爲輸入流
InputStream inputStream = Resources.getResourceAsStream(resource);
//創建XMLMapper的Builder對象
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
//生成我們的mapper對象
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.");
}
}
}
}
}
下面分析這一句,看看如何創建一個Mapper對象的,就是一個mapper.xml文件的抽象。
mapperParser.parse();
/**
下面的方法來自XMLMapperBuilder類
*/
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
/**
負責解析 <mapper></mapper>這一對標籤
<mapper namespace="learn.UserMapper">
<cache/>
<select resultType="learn.User" parameterType="String" id="selectUser">
select * from u_user where usercode = #{id}
</select>
<update parameterType="String" id="updateName">
update u_user set username = #{name} where usercode= #{id}
</update>
<insert parameterType="learn.User" id="insert">
insert into u_user(usercode,username,createtime,usertype,mobile) values (#{userCode},#{userName}
,#{createTime},#{userType},#{mobile});
</insert>
<insert parameterType="learn.User" id="batchInsert">
<foreach item="user" separator="," collection="list">
(#{user.userCode},#{user.userName}
,#{user.createTime},#{user.userType},#{user.mobile})
</foreach>
</insert>
</mapper>
*/
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
//這裏是負責生成二級緩存的,如果有 </cache>標籤就生成一個緩存
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
//這是重頭戲,就是負責生成增刪改查四種類型的sql的抽象的 statement對象
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
/**
爲namespace,生成二級緩存
*/
private void cacheElement(XNode context) {
if (context != null) {
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
//爲當前namespace創建一個緩存,雖然我們的總設置是不使用緩存
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
/** MapperBuilderAssistant類中
生成一個緩存對象,然後放入到configuration對象中*/
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
//這兒,將生成的緩存和當前的這個namespace綁定,就是當前的這個userMapper.xml文件抽象後
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
configuration.addCache(cache);
currentCache = cache;
return cache;
}
下面分析這一句,看看如何創建當前這個mapper.xml中的所有的Statement對象的,就是增刪改查sql語句的抽象。
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
//mybatis-3\src\main\java\org\apache\ibatis\builder\xml\XMLMapperBuilder.java
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
/**
這是遍歷的其中一條
<select resultType="learn.User" parameterType="String" id="selectUser">
select * from u_user where usercode = #{id}
</select>
*/
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {//這裏有遍歷,負責生成這個mapper下的所有sql
//從這裏能夠看出,新建的每一個sql的抽象statement對象都是公用了namespace中的這四個對象
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {//由Builder負責構建一條sql語句
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
下面分析,如何創建某一條sql的抽象對象statement
statementParser.parseStatementNode();
/**
XMLStatementBuilder類中
context 就是:
<select resultType="learn.User" parameterType="String" id="selectUser">
select * from u_user where usercode = #{id}
</select>
*/
public void parseStatementNode(){
//id就是 selectUser
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
//用於判斷,這個sql是否和我們的數據庫一致。比如我們的是mysql,而這條語句配了屬性爲oracle,
//那麼就不執行
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
//節點名稱,我們的是 select
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String lang = context.getStringAttribute("lang");
//用於解析sql
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
//生成我們的動態sql,sql的解析工作
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
生成一個基礎的非動態的sqlSource:RawSqlSource
//XMLScriptBuilder 類中
public SqlSource parseScriptNode() {
//用於判斷是否選擇哪種sqlSorce 如果只有'#{}',也沒有動態標籤時,用RawSqlSource
//'${}'這種的使用DynamicSqlSource
// select * from u_user where usercode = ${id}
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
if (isDynamic) {
/**
對於動態的sqlSource,類似下面這樣的方法getBoundSql()的時候,我們纔會解析
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
*/
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
//非動態的sqlSource,直接在這個方法裏將‘#{}’處理爲佔位符‘?’
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
/**
DynamicSqlSource解析含有${},或動態標籤的sql語句,而RawSqlSource解析只含有#{}的sql語句
*/
//含有${} 的這種只是創建了對象,沒做任何處理。只有我們在調用getBoundSql方法時,會進行解析
//將'${}'替換爲真實的參數值
//parseDynamicTags(context)方法中,只是判斷了是否時動態sqlSource,沒有對'${}'做替換賦值操作
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
}
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> clazz = parameterType == null ? Object.class : parameterType;
//會將 #{} 替換爲 ?
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
}
這倆個類的getBoundSql的代碼如下:
DynamicSqlSource類中的 getBoundSql
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject);
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
RawSqlSource類中的
@Override
public BoundSql getBoundSql(Object parameterObject) {
return sqlSource.getBoundSql(parameterObject);
}
public boolean isDynamic() {
DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
GenericTokenParser parser = createParser(checker);
parser.parse(text);
return checker.isDynamic();
}
private GenericTokenParser createParser(TokenHandler handler) {
return new GenericTokenParser("${", "}", handler);
}
上面是用於解析sql中的“${}”這樣的入參形式。
GenericTokenParser類用於解析“${id}”,“#{id}”這樣的入參,
DynamicCheckerTokenParser 這個是真實的
處理器,它只是進行了判斷是否存在'${}'。
而對‘${}’的賦值操作時用的處理器BindingTokenParser
處理“#{}”的處理器是ParameterMappingTokenHandler(這是一個內部類)
DynamicSqlSource解析含有${}的sql語句,而RawSqlSource解析只含有#{}的sql語句
在我們的getBoundSql的時候,DynamicSqlSource涵蓋的操作比RawSqlSource多了一步,便是優先處理${}字符,動態標籤,然後其本身解析‘#{}’。
另外,類似我們平時使用的 <where>,<foreach>,<trim>這些動態標籤的,最終的解析器也是DynamicSqlSource。具體的判斷邏輯在parseDynamicTags方法中。