项目搭建
参考之前的一篇文章【MyBatis】基本使用
XML解析过程分析
解析入口
- SqlSessionFactoryBean
由于SqlSessionFactoryBean实现了InitializingBean接口,所以Spring加载完SqlSessionFactoryBean实例后会调用afterPropertiesSet方法
org.mybatis.spring.SqlSessionFactoryBean#afterPropertiesSet
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
this.sqlSessionFactory = buildSqlSessionFactory();
}
在xml中配置给SqlSessionFactoryBean
初始化时会自动将配置拷贝到Configuration中,Configuration其实就是MyBatis的一个配置中心。
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
configuration = this.configuration;
if (configuration.getVariables() == null) {
configuration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
configuration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
//解析全局配置文件
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
}
configuration = new Configuration();
if (this.configurationProperties != null) {
configuration.setVariables(this.configurationProperties);
}
}
......
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}
return this.sqlSessionFactoryBuilder.build(configuration);
}
下面详细解释一下其中的几个类
- Configuration
MyBatis配置的一个容器,注册了一些默认的属性,以及后面生成的ResultMap、MappedStatement等都会加入这个容器中
package org.apache.ibatis.session;
public class Configuration {
protected final InterceptorChain interceptorChain = new InterceptorChain();
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
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");
......
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
......
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
}
- ConfigLocation
是指全局配置文件的路径,因为这些配置都可以直接在SqlSessionFactoryBean 中配置所以现在一般都不再直接新增一个Mybatis的配置文件
Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
configuration = this.configuration;
if (configuration.getVariables() == null) {
configuration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
configuration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
}
configuration = new Configuration();
if (this.configurationProperties != null) {
configuration.setVariables(this.configurationProperties);
}
}
- Environment
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
- mapperLocations
mapper文件的路径
开始解析mapper.xml
SqlSessionFactoryBean#buildSqlSessionFactory
中遍历mapperLocations
,并一个一个的解析mapper
文件。
#org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}
#org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//解析mapper.xml
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
//annotation支持
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
Mybatis使用SAX解析xml文件,parser.evalNode("/mapper")
获取mapper
节点的内容。
<mapper namespace="com.excelib.dao.UserMapper">
<insert keyProperty="id" parameterType="User" useGeneratedKeys="true" id="insertUser">
<selectKey order="BEFORE" keyProperty="id" resultType="int">
select if(max(id) is null ,1,max(id)+2) as newId from user
</selectKey>
</insert>
<select resultType="User" parameterType="integer" id="getUser">
select * from user where id = #{id}
</select>
<select parameterType="integer" id="deleteUser">
delete from user where id = #{id}
</select>
</mapper>
依次解析如下的每一个一级节点,XMLMapperBuilder#configurationElement
将Xml配置转换为Java对象。
cache-ref
cache
/mapper/parameterMap
/mapper/resultMap
/mapper/sql
select|insert|update|delete
#org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
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"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//resultMap的解析
resultMapElements(context.evalNodes("/mapper/resultMap"));
//sql标签的解析
sqlElement(context.evalNodes("/mapper/sql"));
//select 等标签的解析
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);
}
}
ResultMap节点解析
重点关注一下resultMap
节点的解析,Mybatis的对ResultMap的解析步骤如下
- 遍历XML中所有的ResultMap节点
- 读取ResultMap节点的属性
- 遍历ResultMap的子节点,将数据库的每一列的映射关系解析成一个ResultMapping对象,添加到集合resultMappings中
- 将resultMappings封装到ResultMap中,构造ResultMap对象
- 以ResultMap.getId()为key,将ResultMap添加到Configuration.resultMaps中
- 读取
<resultMap/>
节点数据
由于一个mapper中可以含有多个resultMap 所以resultMapElements(context.evalNodes("/mapper/resultMap"));
中context.evalNodes("/mapper/resultMap")
的解析结果其实是一个集合,如下:
[<resultMap type="com.excelib.User" id="userMap">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
<result column="sex" property="sex"/>
</resultMap>]
- 生成ResultMapping
开始具体的解析过程,读取resultMap 的子节点生成resultMapping对象,将所有的ResultMapping存放到一个List集合resultMappings
中。
#org.apache.ibatis.builder.xml.XMLMapperBuilder#resultMapElements
//遍历所有的resultMap节点
private void resultMapElements(List<XNode> list) throws Exception {
for (XNode resultMapNode : list) {
try {
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
}
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
//读取resultMap节点的属性
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
String extend = resultMapNode.getStringAttribute("extends");
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();
// 遍历resultMap的子节点,数据库的每一列的映射关系解析成resultMapping对象
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List<ResultFlag> flags = new ArrayList<ResultFlag>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
//解析resultMapping
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
//将resultMappings封装到ResultMap中
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
XMLMapperBuilder#buildResultMappingFromContext
将每一个<result column="age" property="age"/>
解析成一个ResultMapping
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
String property;
if (flags.contains(ResultFlag.CONSTRUCTOR)) {
property = context.getStringAttribute("name");
} else {
property = context.getStringAttribute("property");
}
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
String nestedSelect = context.getStringAttribute("select");
String nestedResultMap = context.getStringAttribute("resultMap",
processNestedResultMappings(context, Collections.<ResultMapping> emptyList()));
String notNullColumn = context.getStringAttribute("notNullColumn");
String columnPrefix = context.getStringAttribute("columnPrefix");
String typeHandler = context.getStringAttribute("typeHandler");
String resultSet = context.getStringAttribute("resultSet");
String foreignColumn = context.getStringAttribute("foreignColumn");
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
Class<?> javaTypeClass = resolveClass(javaType);
@SuppressWarnings("unchecked")
Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
- 生成ResultMap并加入Configuration
实际上是加入了Configuration.resultMaps
这个map中,key默认是xml中的id+当前的namespace
#org.apache.ibatis.builder.ResultMapResolver#resolve
public ResultMap resolve() {
return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
}
#org.apache.ibatis.builder.MapperBuilderAssistant#addResultMap
public ResultMap addResultMap(
String id,
Class<?> type,
String extend,
Discriminator discriminator,
List<ResultMapping> resultMappings,
Boolean autoMapping) {
......
//获取该resultMap 的id,默认是xml中的id+namespace
id = applyCurrentNamespace(id, false);
//创建ResultMap对象
ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
.discriminator(discriminator)
.build();
//将resultMap添加到configuration中
configuration.addResultMap(resultMap);
return resultMap;
}
#org.apache.ibatis.session.Configuration#addResultMap
protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
public void addResultMap(ResultMap rm) {
resultMaps.put(rm.getId(), rm);
checkLocallyForDiscriminatedNestedResultMaps(rm);
checkGloballyForDiscriminatedNestedResultMaps(rm);
}
select|insert|update|delete 标签的解析
- 解析select|insert|update|delete等xml标签生成MixedSqlNode;
- 封装SqlNode到sqlSource中。SqlSource实际上只是对SqlNode的封装并没有实际生成Sql语句,因为MyBatis的动态Sql是根据查询条件动态拼接生成最终数据库执行的Sql;
- 根据解析到的sqlSource,resultMap等配置创建MappedStatement并加入Configuration中,实际上加入了Configuration.mappedStatements这个map中。每一个select|insert|update|delete节点都对应一个MappedStatement。
使用buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
来遍历并解析解析mapper中的每一个select|insert|update|delete
节点
#org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>)`
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
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);
}
}
}
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
//匹配databaseId,如果不匹配则不解析该sql
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
//获取resultType对应的Class
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
//节点名称select|insert|update|delete
String nodeName = context.getNode().getNodeName();
//sqlCommandType 是指sql类型,如insert,update等
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());
// Parse selectKey after includes and remove them.
//解析selectkey标签
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
//提取sqlSource
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
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;
}
//生成MappedStatement并加入configuration中
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
- 解析Sql类型
上面的代码中已经解析出了当前sql的类型select|insert|update|delete
#org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
public enum SqlCommandType {
UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
}
- 解析SqlNode生成SqlSource
SqlSource实际上只是对SqlNode的封装,并没有实际生成Sql语句,因为MyBatis的动态Sql是根据查询条件动态拼接生成最终数据库执行的Sql;
#org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
//XMLLanguageDriver#createSqlSource(Configuration, XNode, java.lang.Class<?>)
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
#org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode
public SqlSource parseScriptNode() {
//解析xml节点封装成MixedSqlNode
MixedSqlNode rootSqlNode = parseDynamicTags(context);
//生成sqlSource
SqlSource sqlSource = null;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
- 解析SqlNode
将sql中的每一个节点都解析为一个特定的SqlNode,并判断子节点是否是普通的文本节点,如果是动态节点(包含${})则封装成TextSqlNode
如果不是封装成StaticTextSqlNode
如果不是普通文本节点则,则递归解析动态子节点
#org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseDynamicTags
protected MixedSqlNode parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<SqlNode>();
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) { // issue #628
String nodeName = child.getNode().getNodeName();
//寻找NodeHandler
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);
}
上面的解析过程有一点复杂,我们用下面的sql来看一下它的解析过程
<update id="updateUser" parameterType="com.excelib.User">
update user
<set>
<if test="name!=null and name !=''">
name = #{name},
</if>
<if test="age!=null and age !=''">
age = #{age}
</if>
</set>
where id = #{id}
</update>
在parseDynamicTags
方法中NodeList children = node.getNode().getChildNodes();
其实拿到的是上面sql的3个子节点的内容
update user |
<set><if test="name!=null and name !=''"> name = #{name},</if><if test="age!=null and age !=''">age = #{age}</if></set> |
where id = #{id} |
依次遍历三个子节点,根据判定条件封装成不同的sqlNode对象
sqlNode解析步骤
update user
判断child.getNode().getNodeType() == Node.TEXT_NODE
成立,即为文本节点;然后调用textSqlNode.isDynamic()
去判断该节点是否是动态的,这条语句中不包含${}
所以不是动态的(GenericTokenParser("${", "}", handler)
),最终会调用contents.add(new StaticTextSqlNode(data));
;<set>
child.getNode().getNodeType() == Node.ELEMENT_NODE
这个判断是成立,即是它动态节点;然后根据nodeName从nodeHandlerMap查找nodeHandler并调用handler.handleNode(child, contents);
进行动态节点解析。
//org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#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());
}
这里的nodeName就是set
,所以会查找到SetHandler
来解析,看一下它的解析方法
//org.apache.ibatis.scripting.xmltags.XMLScriptBuilder.SetHandler#handleNode
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
SetSqlNode set = new SetSqlNode(configuration, mixedSqlNode);
targetContents.add(set);
}
这里又递归调用了parseDynamicTags()
解析set节点的子节点,并封装成MixedSqlNode
返回。可以预期在这次调用parseDynamicTags()中会有2个if
子节点,然后每个if
节点又会调用IfHandler.handleNode
解析子元素,if
的子节点只有文本节点所以解析终止返回一个文本节点,最终一层一层返回。
//org.apache.ibatis.scripting.xmltags.XMLScriptBuilder.IfHandler#handleNode
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
//读取if节点的test属性
String test = nodeToHandle.getStringAttribute("test");
IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
targetContents.add(ifSqlNode);
}
-
where
也判断child.getNode().getNodeType() == Node.TEXT_NODE
成立,即为文本节点,会调用contents.add(new StaticTextSqlNode(data));
最终会都会加入contents并返回new MixedSqlNode(contents);
所以最终解析结果,如下:
sql语句 | 节点类型 |
---|---|
update user | StaticTextSqlNode |
<set> | SetSqlNode |
<if test="name!=null and name !=''"> | IfSqlNode |
name = #{name}, | StaticTextSqlNode |
</if> | |
<if test="age!=null and age !=''"> | IfSqlNode |
age = #{age} | StaticTextSqlNode |
</if> | |
</set> | |
where id = #{id} | StaticTextSqlNode |
调试结果也正是这样
接下来通过new DynamicSqlSource(configuration, rootSqlNode);
封装成SqlSource。
最后将insert|update等解析结果封装成MappedStatement并加入Configuration中builderAssistant.addMappedStatement(...);
//MapperBuilderAssistant#addMappedStatement(.....)
public MappedStatement addMappedStatement(...) {
......
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
//获取resultMap
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
注解支持
前面分析了configurationElement(parser.evalNode("/mapper"));
解析mapper.xml。MyBatis其实也提供注解支持。
MyBatis 常用注解
- @SelectProvider
动态调用某类的某个方法,并使用其返回值作为sql语句- @Select
使用注解value值作为sql语句- @Select({"<script>",sql,"</script>"})
动态解析value值生成动态Sql- @Options
该MappedStatement的配置参数,如:cache timeout等
具体使用可以参考【MyBatis】基本使用
注解使用示例:
public interface UserMapper {
public void insertUser(User user);
public User getUser(Integer id);
public void updateUser(User user);
public void deleteUser(Integer id);
/**
* 标识提供sql语句的类和方法,mybatis会调用该类的方法获取sql语句,生成MappedStatement注册
*/
@SelectProvider(type = SqlProvider.class,method = "getQueryUserSql")
public User queryUserWithSqlProvider(@Param("id") Integer id);
/**
* 使用注解配置sql语句
*/
@Select("select * from user where id = #{id}")
public User queryUserWithAnnotation(@Param("id") Integer id);
/**
* 添加<script> 可以则sql语句可以使用动态标签
*/
@Select({"<script>",
" update user\n" +
" <set>\n" +
" <if test=\"name!=null and name !=''\">\n" +
" name = #{name},\n" +
" </if>\n" +
" <if test=\"age!=null and age !=''\">\n" +
" age = #{age}\n" +
" </if>\n" +
" </set>\n" +
" where id = #{id}\n",
"</script>"})
public void updateUserWithAnnotation(@Param("id") Integer id,@Param("name")String name,@Param("age")Integer age);
}
回到一开始解析mapper.xml的入口的地方 XMLMapperBuilder#parse
,其中的 bindMapperForNamespace();
就是提供注解支持的入口。首先获取mapper.xml的Namespace,根据nameSpace的值加载Class对象(其实是一个接口)。我们要处理的对象就是该接口里所有方法的注解。
#org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//解析mapper.xml
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
//annotation支持
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
private void bindMapperForNamespace() {
//获取当前的Namespace,根据nameSpace的值生成Class,其实是Mapper接口的Class
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
//判断是否存储了mapper工厂
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
//将class加入Configuration,里面会有对mapper接口的解析
configuration.addMapper(boundType);
}
}
}
}
configuration.addMapper(boundType)
中提供了对注解mapper的解析,使用MapperAnnotationBuilder.parse()
来对该接口的方法上的annotation进行解析,而且使用knownMappers来保存mapper代理工厂,key就是这个mapper接口类型(nameSpace)。这个MapperProxyFactory
会用来产生动态代理bean(注入到Spring),当我们调用bean的方法的时候就会回调代理的invoke方法,在该方法中查找mappedStatement来组装sql并调用。
//org.apache.ibatis.session.Configuration#addMapper
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
//org.apache.ibatis.binding.MapperRegistry#addMapper
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
具体的解析过程
- 遍历接口的每一个方法
- 首先根据方法注解创建出一个SqlSource。因为涉及到动态sql的解析,所以这一步也比较复杂;
- 然后获取可以识别的注解,提取注解配置的属性到对应的Java对象中;
- 最终生成一个MappedStatement并注册到Configuration中;
public class MapperAnnotationBuilder {
//org.apache.ibatis.builder.annotation.MapperAnnotationBuilder
public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
String resource = type.getName().replace('.', '/') + ".java (best guess)";
this.assistant = new MapperBuilderAssistant(configuration, resource);
this.configuration = configuration;
this.type = type;
sqlAnnotationTypes.add(Select.class);
sqlAnnotationTypes.add(Insert.class);
sqlAnnotationTypes.add(Update.class);
sqlAnnotationTypes.add(Delete.class);
sqlProviderAnnotationTypes.add(SelectProvider.class);
sqlProviderAnnotationTypes.add(InsertProvider.class);
sqlProviderAnnotationTypes.add(UpdateProvider.class);
sqlProviderAnnotationTypes.add(DeleteProvider.class);
}
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
void parseStatement(Method method) {
Class<?> parameterTypeClass = getParameterType(method);
LanguageDriver languageDriver = getLanguageDriver(method);
//根据注解创建SqlSource
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
//解析@Options
Options options = method.getAnnotation(Options.class);
......
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
// first check for SelectKey annotation - that overrides everything else
//解析@SelectKey
SelectKey selectKey = method.getAnnotation(SelectKey.class);
......
} else {
keyGenerator = NoKeyGenerator.INSTANCE;
}
if (options != null) {
......
}
String resultMapId = null;
//解析@ResultMap
ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
if (resultMapAnnotation != null) {
......
} else if (isSelect) {
resultMapId = parseResultMap(method);
}
assistant.addMappedStatement(......);
}
}
}
从注解中生成SqlSource的过程,分为两种情况:
一种是 @Select @Insert @Update @Delete
注解
一种是使用SelectSqlProvider、InsertSqlProvider、UpdateSqlProvider 、DeleteSqlProvider
提供Sql语句 。
//从Annotation中提取Sql语句并交给XMLLanguageDriver解析
private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
try {
Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
if (sqlAnnotationType != null) {
//如果是 Select.class Insert.class Update.class Delete.class 则直接将value拿去解析
if (sqlProviderAnnotationType != null) {
throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
}
Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
} else if (sqlProviderAnnotationType != null) {
//如果是 SelectSqlProvider.class InsertSqlProvider.class UpdateSqlProvider.class DeleteSqlProvider.class 等类型则封装成ProviderSqlSource
Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method);
}
return null;
} catch (Exception e) {
throw new BuilderException("Could not find value method on SQL annotation. Cause: " + e, e);
}
}
private SqlSource buildSqlSourceFromStrings(String[] strings, Class<?> parameterTypeClass, LanguageDriver languageDriver) {
final StringBuilder sql = new StringBuilder();
for (String fragment : strings) {
sql.append(fragment);
sql.append(" ");
}
return languageDriver.createSqlSource(configuration, sql.toString().trim(), parameterTypeClass);
}
- 如果是
@Select @Insert @Update @Delete
则直接将value拿去XMLLanguageDriver
解析,会判断如果sql是以<script>
开头则动态解析sql的子节点,如果不是则直接封装成SqlSource
。关于动态解析子节点的直接参考前面mapper.xml 中insert|update
等标签的解析过程(递归生成SqlNode)。
/**
* @author Eduardo Macarron
*/
public class XMLLanguageDriver implements LanguageDriver {
@Override
public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
}
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
@Override
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
// issue #3
if (script.startsWith("<script>")) {
XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
} else {
// issue #127
script = PropertyParser.parse(script, configuration.getVariables());
TextSqlNode textSqlNode = new TextSqlNode(script);
if (textSqlNode.isDynamic()) {
return new DynamicSqlSource(configuration, textSqlNode);
} else {
return new RawSqlSource(configuration, script, parameterType);
}
}
}
}
- 如果是
SelectSqlProvider InsertSqlProvider UpdateSqlProvider DeleteSqlProvider
等类型则封装成ProviderSqlSource。在Excutor执行数据库操作的时候会调用SqlSource.getBoundSql(),这时会首先会从parameterObject抽取出调用参数封装成Map,然后使用该参数反射调用SqlProvider中指定的方法生成sql。
public class ProviderSqlSource implements SqlSource {
@Override
public BoundSql getBoundSql(Object parameterObject) {
SqlSource sqlSource = createSqlSource(parameterObject);
return sqlSource.getBoundSql(parameterObject);
}
private SqlSource createSqlSource(Object parameterObject) {
try {
int bindParameterCount = providerMethodParameterTypes.length - (providerContext == null ? 0 : 1);
String sql;
if (providerMethodParameterTypes.length == 0) {
sql = invokeProviderMethod();
} else if (bindParameterCount == 0) {
sql = invokeProviderMethod(providerContext);
} else if (bindParameterCount == 1 &&
(parameterObject == null || providerMethodParameterTypes[(providerContextIndex == null || providerContextIndex == 1) ? 0 : 1].isAssignableFrom(parameterObject.getClass()))) {
sql = invokeProviderMethod(extractProviderMethodArguments(parameterObject));
} else if (parameterObject instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> params = (Map<String, Object>) parameterObject;
sql = invokeProviderMethod(extractProviderMethodArguments(params, providerMethodArgumentNames));
} else {
......
}
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
return sqlSourceParser.parse(replacePlaceholder(sql), parameterType, new HashMap<String, Object>());
} catch (BuilderException e) {
throw e;
} catch (Exception e) {
......
}
}
private Object[] extractProviderMethodArguments(Object parameterObject) {
if (providerContext != null) {
Object[] args = new Object[2];
args[providerContextIndex == 0 ? 1 : 0] = parameterObject;
args[providerContextIndex] = providerContext;
return args;
} else {
return new Object[] { parameterObject };
}
}
private Object[] extractProviderMethodArguments(Map<String, Object> params, String[] argumentNames) {
Object[] args = new Object[argumentNames.length];
for (int i = 0; i < args.length; i++) {
if (providerContextIndex != null && providerContextIndex == i) {
args[i] = providerContext;
} else {
args[i] = params.get(argumentNames[i]);
}
}
return args;
}
private String invokeProviderMethod(Object... args) throws Exception {
Object targetObject = null;
if (!Modifier.isStatic(providerMethod.getModifiers())) {
targetObject = providerType.newInstance();
}
CharSequence sql = (CharSequence) providerMethod.invoke(targetObject, args);
return sql != null ? sql.toString() : null;
}
}
annotation解析工作都完成后,生成一个MappedStatement并注册到Configuration中。其实无论是解析注解还是解析mapper.xml最终都是一个insert|update|delete|select
方法或者节点生成一个SqlSource来构造一个MappedStatement并注册到Configuration中。