解析映射配置文件
SQL 映射文件只有很少的几个顶级元素(按照定义顺序列出)
select 元素允许你配置很多属性来配置每条语句的行为细节
< select
id = " select"
parameterType = " int"
parameterMap = " deprecated"
resultType = " hashmap"
resultMap = " personResultMap"
flushCache = " false"
useCache = " true"
timeout = " 10"
fetchSize = " 256"
statementType = " PREPARED"
resultSetType = " FORWARD_ONLY" >
< insert
id = " insert"
parameterType = " com.itheima.pojo.User"
flushCache = " true"
statementType = " PREPARED"
keyProperty = " "
keyColumn = " "
useGeneratedKeys = " "
timeout = " 20" >
< update
id = " update"
parameterType = " com.itheima.pojo.User"
flushCache = " true"
statementType = " PREPARED"
timeout = " 20" >
< delete
id = " delete"
parameterType = " com.itheima.pojo.User"
flushCache = " true"
statementType = " PREPARED"
timeout = " 20" >
动态sql ,借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类
< select id = " findActiveBlogLike"
resultType = " Blog" >
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
< choose>
< when test = " title != null" >
AND title like #{title}
</ when>
< when test = " author != null and author.name != null" >
AND author_name like #{author.name}
</ when>
< otherwise>
AND featured = 1
</ otherwise>
</ choose>
</ select>
问题:映射配置文件中标签和属性如何被解析封装的?
继续回到 XMLConfigBuilder 代码中,这里先通过解析 /mappers 节点,然后遍历下面的子标签 /mapper
public class XMLConfigBuilder extends BaseBuilder {
. . .
protected final Configuration configuration;
. . .
private void mapperElement ( XNode parent) throws Exception {
if ( parent != null ) {
for ( XNode child : parent. getChildren ( ) ) {
if ( "package" . equals ( child. getName ( ) ) ) {
. . .
} else {
. . .
if ( resource != null && url == null && mapperClass == null ) {
ErrorContext . instance ( ) . resource ( resource) ;
try ( InputStream inputStream = Resources . getResourceAsStream ( resource) ) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder ( inputStream, configuration, resource, configuration. getSqlFragments ( ) ) ;
. . .
}
} else if ( resource == null && url != null && mapperClass == null ) {
. . .
} else if ( resource == null && url == null && mapperClass != null ) {
. . .
} else {
. . .
}
}
}
}
}
}
这时候也先创建一个 XMLMapperBuilder ,然后把 UserMapper.xml 解析成 document 对象,封装到 XPathParser 里面
public class XMLMapperBuilder extends BaseBuilder {
. . .
public XMLMapperBuilder ( InputStream inputStream, Configuration configuration, String resource, Map < String , XNode > sqlFragments) {
this ( new XPathParser ( inputStream, true , configuration. getVariables ( ) , new XMLMapperEntityResolver ( ) ) ,
configuration, resource, sqlFragments) ;
}
}
然后通过 XMLMapperBuilder 开始 parse()
public class XMLConfigBuilder extends BaseBuilder {
. . .
private void mapperElement ( XNode parent) throws Exception {
if ( parent != null ) {
for ( XNode child : parent. getChildren ( ) ) {
if ( "package" . equals ( child. getName ( ) ) ) {
. . .
} else {
. . .
if ( resource != null && url == null && mapperClass == null ) {
ErrorContext . instance ( ) . resource ( resource) ;
try ( InputStream inputStream = Resources . getResourceAsStream ( resource) ) {
. . .
mapperParser. parse ( ) ;
}
} else if ( resource == null && url != null && mapperClass == null ) {
. . .
} else if ( resource == null && url == null && mapperClass != null ) {
. . .
} else {
. . .
}
}
}
}
}
}
进入 parse() 中,先判断资源对象 resource ,即 mapper/UserMapper.xml 是不是已经加载过,没有则加载,并且从文件的根节点 /mapper 开始解析,解析完之后标记解析完成
public class XMLMapperBuilder extends BaseBuilder {
. . .
public void parse ( ) {
if ( ! configuration. isResourceLoaded ( resource) ) {
configurationElement ( parser. evalNode ( "/mapper" ) ) ;
configuration. addLoadedResource ( resource) ;
bindMapperForNamespace ( ) ;
}
}
解析的核心代码就在 configurationElement() 里面,就是解析 /mapper 中各种专属标签
public class XMLMapperBuilder extends BaseBuilder {
. . .
protected final Configuration configuration;
private final MapperBuilderAssistant builderAssistant;
. . .
private void configurationElement ( XNode context) {
try {
String namespace = context. getStringAttribute ( "namespace" ) ;
if ( namespace == null || namespace. isEmpty ( ) ) {
throw new BuilderException ( "Mapper's namespace cannot be empty" ) ;
}
builderAssistant. setCurrentNamespace ( namespace) ;
cacheRefElement ( context. evalNode ( "cache-ref" ) ) ;
cacheElement ( context. evalNode ( "cache" ) ) ;
parameterMapElement ( context. evalNodes ( "/mapper/parameterMap" ) ) ;
resultMapElements ( context. evalNodes ( "/mapper/resultMap" ) ) ;
sqlElement ( context. evalNodes ( "/mapper/sql" ) ) ;
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) ;
}
}
}
现从解析 select|insert|update|delete 语句 开始看看,首先判断有没有配置过 databaseId,现在是没配置过的,然后直接解析下面的 buildStatementFromContext() 来获取 MappedStatement
public class XMLMapperBuilder extends BaseBuilder {
. . .
private void buildStatementFromContext ( List < XNode > list) {
if ( configuration. getDatabaseId ( ) != null ) {
buildStatementFromContext ( list, configuration. getDatabaseId ( ) ) ;
}
buildStatementFromContext ( list, null ) ;
}
}
在执行 buildStatementFromContext() 时,会把解析出来的 select|insert|update|delete 语句 交给 XMLStatementBuilder 进行处理(到这发现了,myBatis 所有的具体解析都交给 XML**Builder 来处理),通过 XMLStatementBuilder 解析出来 MappedStatement 对象
public class XMLMapperBuilder extends BaseBuilder {
. . .
private void buildStatementFromContext ( List < XNode > list, String requiredDatabaseId) {
for ( XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder ( configuration, builderAssistant, context, requiredDatabaseId) ;
try {
statementParser. parseStatementNode ( ) ;
} catch ( IncompleteElementException e) {
configuration. addIncompleteStatement ( statementParser) ;
}
}
}
}
通过 parseStatementNode() ,可以看到里面开始解析像 id 属性 、databaseId 属性 、flushCache 属性 等,然后通过 builderAssistant 对象,构建 MappedStatement 对象
public class XMLStatementBuilder extends BaseBuilder {
. . .
protected final Configuration configuration;
private final MapperBuilderAssistant builderAssistant;
. . .
public void parseStatementNode ( ) {
String id = context. getStringAttribute ( "id" ) ;
String databaseId = context. getStringAttribute ( "databaseId" ) ;
if ( ! databaseIdMatchesCurrent ( id, databaseId, this . requiredDatabaseId) ) {
return ;
}
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 ) ;
XMLIncludeTransformer includeParser = new XMLIncludeTransformer ( configuration, builderAssistant) ;
includeParser. applyIncludes ( context. getNode ( ) ) ;
String parameterType = context. getStringAttribute ( "parameterType" ) ;
Class < ? > parameterTypeClass = resolveClass ( parameterType) ;
String lang = context. getStringAttribute ( "lang" ) ;
LanguageDriver langDriver = getLanguageDriver ( lang) ;
processSelectKeyNodes ( id, parameterTypeClass, langDriver) ;
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;
}
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) ;
}
}
builderAssistant 对象,把 MappedStatement 对象放入 Configuration 中
public class MapperBuilderAssistant extends BaseBuilder {
protected final Configuration configuration;
. . .
public MappedStatement addMappedStatement (
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class < ? > parameterType,
String resultMap,
Class < ? > resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {
. . .
configuration. addMappedStatement ( statement) ;
return statement;
}
}
总结
问题:sql 占位符如何进行的替换?动态 sql 如何进行的解析?
继续来看 XMLStatementBuilder 是怎么解析 <select>、<insert>、<update>、<delete> 标签的,首先先获取一个能解析内容(是 xml 还是其他语言的写的)的驱动器
public class XMLStatementBuilder extends BaseBuilder {
. . .
public void parseStatementNode ( ) {
. . .
LanguageDriver langDriver = getLanguageDriver ( lang) ;
. . .
}
}
驱动器如下
因为初始化Configuration 已经对这些驱动器初始化过,就从里面获取对应驱动器
public class XMLStatementBuilder extends BaseBuilder {
. . .
private LanguageDriver getLanguageDriver ( String lang) {
Class < ? extends LanguageDriver > langClass = null ;
if ( lang != null ) {
langClass = resolveClass ( lang) ;
}
return configuration. getLanguageDriver ( langClass) ;
}
}
从代码层面输入是 null,就获取默认的内容驱动器。即 XMLLanguageDriver
public class Configuration {
. . .
public LanguageDriver getLanguageDriver ( Class < ? extends LanguageDriver > langClass) {
if ( langClass == null ) {
return languageRegistry. getDefaultDriver ( ) ;
}
languageRegistry. register ( langClass) ;
return languageRegistry. getDriver ( langClass) ;
}
}
拿到驱动驱动器以后,开始解析 Xnode 的 context 内容了
public class XMLStatementBuilder extends BaseBuilder {
. . .
public void parseStatementNode ( ) {
. . .
SqlSource sqlSource = langDriver. createSqlSource ( configuration, context, parameterTypeClass) ;
. . .
}
}
其中 context 封装的是例如下面的这样的语句,parameterTypeClass 就是参数的类型
< select resultType = " com.itheima.pojo.User" parameterType = " int" id = " findUserById" >
SELECT id, name FROM user WHERE id = #{id}
</ select>
XMLLanguageDriver 首先创建一个 XMLScriptBuilder 来辅助解析
public class XMLLanguageDriver implements LanguageDriver {
. . .
@Override
public SqlSource createSqlSource ( Configuration configuration, XNode script, Class < ? > parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder ( configuration, script, parameterType) ;
return builder. parseScriptNode ( ) ;
}
}
在创建 XMLScriptBuilder 时,先初始化各种动态 sql 标签的处理器,然后返回对象
public class XMLScriptBuilder extends BaseBuilder {
. . .
private final Map < String , NodeHandler > nodeHandlerMap = new HashMap < > ( ) ;
. . .
public XMLScriptBuilder ( Configuration configuration, XNode context, Class < ? > parameterType) {
super ( configuration) ;
this . context = context;
this . parameterType = parameterType;
initNodeHandlerMap ( ) ;
}
private void initNodeHandlerMap ( ) {
nodeHandlerMap. put ( "trim" , new TrimHandler ( ) ) ;
nodeHandlerMap. put ( "where" , new WhereHandler ( ) ) ;
nodeHandlerMap. put ( "set" , new SetHandler ( ) ) ;
nodeHandlerMap. put ( "foreach" , new ForEachHandler ( ) ) ;
nodeHandlerMap. put ( "if" , new IfHandler ( ) ) ;
nodeHandlerMap. put ( "choose" , new ChooseHandler ( ) ) ;
nodeHandlerMap. put ( "when" , new IfHandler ( ) ) ;
nodeHandlerMap. put ( "otherwise" , new OtherwiseHandler ( ) ) ;
nodeHandlerMap. put ( "bind" , new BindHandler ( ) ) ;
}
}
XMLLanguageDriver 就会利用 XMLScriptBuilder 解析 context 内容,首先先解析出动态节点
public class XMLScriptBuilder extends BaseBuilder {
. . .
public SqlSource parseScriptNode ( ) {
MixedSqlNode rootSqlNode = parseDynamicTags ( context) ;
SqlSource sqlSource;
if ( isDynamic) {
. . .
} else {
. . .
}
return sqlSource;
}
}
在 parseDynamicTags() 中,会遍历 context 的子节点,包括判断子节点是 CDATA_SECTION_NODE
类型、还是纯文本 TEXT_NODE
还是动态那种 ELEMENT_NODE
类型,分别进行不同解析。因为现在的子节点就 <#text> SELECT id, name FROM user WHERE id = #{id} </#text> 这样的文本内容,同时没带有 ${}
这样的替换符就只能能表示静态节点,直接加到 contents 中,完成后封装成 MixedSqlNode 返回
public class XMLScriptBuilder extends BaseBuilder {
. . .
private final XNode context;
private boolean isDynamic;
private final Class < ? > parameterType;
. . .
protected MixedSqlNode parseDynamicTags ( XNode node) {
List < SqlNode > contents = new ArrayList < > ( ) ;
NodeList children = node. getNode ( ) . getChildNodes ( ) ;
for ( int i = 0 ; i < children. getLength ( ) ; i++ ) {
XNode child = node. newXNode ( children. item ( i) ) ;
if ( child. getNode ( ) . getNodeType ( ) == Node . CDATA_SECTION_NODE || child. getNode ( ) . getNodeType ( ) == Node . TEXT_NODE) {
String data = child. getStringBody ( "" ) ;
TextSqlNode textSqlNode = new TextSqlNode ( data) ;
if ( textSqlNode. isDynamic ( ) ) {
contents. add ( textSqlNode) ;
isDynamic = true ;
} else {
contents. add ( new StaticTextSqlNode ( data) ) ;
}
} else if ( child. getNode ( ) . getNodeType ( ) == Node . ELEMENT_NODE) {
String nodeName = child. getNode ( ) . getNodeName ( ) ;
NodeHandler handler = nodeHandlerMap. get ( nodeName) ;
if ( handler == null ) {
throw new BuilderException ( "Unknown element <" + nodeName + "> in SQL statement." ) ;
}
handler. handleNode ( child, contents) ;
isDynamic = true ;
}
}
return new MixedSqlNode ( contents) ;
}
}
获取 **MixedSqlNode ** 之后,如果是节点包含 ${}
或者动态 sql 还得进一步解析获取 DynamicSqlSource ,现在只是简单文本类型直接获取 RawSqlSource 即可
public class XMLScriptBuilder extends BaseBuilder {
. . .
public SqlSource parseScriptNode ( ) {
MixedSqlNode rootSqlNode = parseDynamicTags ( context) ;
SqlSource sqlSource;
if ( isDynamic) {
sqlSource = new DynamicSqlSource ( configuration, rootSqlNode) ;
} else {
sqlSource = new RawSqlSource ( configuration, rootSqlNode, parameterType) ;
}
return sqlSource;
}
}
其中,创建 RawSqlSource 时,里面会创建 SqlSourceBuilder 对象,来解析 SQL 语句 SELECT id, name FROM user WHERE id = #{id},并传入对应参数类型。主要是为了替换 #{id}
为 ?
,然后那到 id
属性,以及标记它的类型,存到 ParameterMapping 中
public class RawSqlSource implements SqlSource {
. . .
private final SqlSource sqlSource;
. . .
public RawSqlSource ( Configuration configuration, SqlNode rootSqlNode, Class < ? > parameterType) {
this ( configuration, getSql ( configuration, rootSqlNode) , parameterType) ;
}
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 < > ( ) ) ;
}
}
下面就是一些解析工作原理,先创建一个 ParameterMappingTokenHandler ,处理分词后的 token ,然后利用 GenericTokenParser 解析#{}
以及内容,最后封装到 StaticSqlSource 中
public class SqlSourceBuilder extends BaseBuilder {
. . .
public SqlSource parse ( String originalSql, Class < ? > parameterType, Map < String , Object > additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler ( configuration, parameterType, additionalParameters) ;
GenericTokenParser parser = new GenericTokenParser ( "#{" , "}" , handler) ;
String sql;
if ( configuration. isShrinkWhitespacesInSql ( ) ) {
sql = parser. parse ( removeExtraWhitespaces ( originalSql) ) ;
} else {
sql = parser. parse ( originalSql) ;
}
return new StaticSqlSource ( configuration, sql, handler. getParameterMappings ( ) ) ;
}
}
其中,分析器先拆分工作,先找到对应的 ${}
,并把分离出来的 id
文本,交给 ParameterMappingTokenHandler 处理
public class GenericTokenParser {
. . .
public String parse ( String text) {
. . .
do {
. . .
if ( end == - 1 ) {
. . .
} else {
builder. append ( handler. handleToken ( expression. toString ( ) ) ) ;
offset = end + closeToken. length ( ) ;
}
}
start = text. indexOf ( openToken, offset) ;
} while ( start > - 1 ) ;
. . .
return builder. toString ( ) ;
}
}
ParameterMappingTokenHandler 会把 id
这个文本存入自己的 parameterMappings 属性中,然后返回替换文本 ? ,最后把预编译的 sql 和 parameterMappings 一起封装到 StaticSqlSource 中
public class SqlSourceBuilder extends BaseBuilder {
. . .
private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {
. . .
@Override
public String handleToken ( String content) {
parameterMappings. add ( buildParameterMapping ( content) ) ;
return "?" ;
}
}
}
得到替换后的 sqlSource 后,同时检查 Xnode 的 context 节点属于哪种 StatementType ,然后借助 builderAssistant 创建 MappedStatement 对象
public class XMLStatementBuilder extends BaseBuilder {
. . .
public void parseStatementNode ( ) {
. . .
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" ) ;
. . .
builderAssistant. addMappedStatement ( id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets) ;
. . .
}
}
总结