Mybatis动态解析
Mybatis配置文件:
mybatis配置文件在src/main/resources目录下,假设是mybatisConfig.xml(名称随意)。
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "mybatis-3-config.dtd" >
<configuration>
<!-- 可以配置多个运行环境,但是每个SqlSessionFactory 实例只能选择一个运行环境 -->
<environments default="work">
<environment id="work">
<!-- 两种事务管理器类型:JDBC和MANAGED -->
<transactionManager type="JDBC"></transactionManager>
<!-- 有三种数据源类型:UNPOOLED、POOLED、JNDI -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root" />
<property name="password" value="root123" />
</dataSource>
</environment>
</environments>
<mappers>
<!-- 可配置多个mapper -->
<mapper resource="com/example/mapper/StudentMapper.xml"/>
</mappers>
</configuration>
如果是spring与mybatis整合,可以将数据源配置放到spring配置文件中。另外,如果mapper接口和mapper配置文件遵循:mapper.java和mapper.xml的文件名相同且在同一个文件夹下,在mybatis配置文件中可以不用配置mapper。
Mapper配置文件:
StudentMapper.xml配置文件在com/example/mapper目录下。
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd" >
<mapper namespace="com.example.mapper.StudentMapper">
<resultMap id="studentResultMap" type="com.example.entity.Student">
<id column="stu_id" property="id"/>
<result column="stu_name" property="name"/>
<result column="stu_password" property="password"/>
</resultMap>
<insert id="saveStudent" parameterType="com.example.entity.Student">
insert into student values(default,#{name},#{password})
</insert>
<select id="selectStudent" parameterType="com.example.entity.Student" resultMap="studentResultMap">
select id,name from student where id = #{id}
</select>
</mapper>
实体类:
package com.example.entity;
public class Student {
private int id;
private String name;
private String password;
public Student() {
}
//省略了getter、setter方法
}
对于上面mapper.xml配置文件中的两条SQL,mybatis在底层是如何解析的呢?
是通过XMLMapperBuilder解析mapper.xml文件的,具体源码见下文分析。
在单独使用mybatis时,SqlSessionFactory和SqlSession是mybatis的核心,SqlSessionFactory用于生成SqlSession。Spring与mybatis整合之后,通过SqlSessionFactoryBean创建SqlSessionFactory ,Spring与Mybatis整合时需要配置SqlSessionFactoryBean,该配置会加入数据源和mybatis xml配置文件路径等信息:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatisConfig.xml"/>
<property name="mapperLocations" value="classpath*:com/example/mapper/*.xml"/>
</bean>
SqlSessionFactoryBean实现了Spring的InitializingBean接口,该接口的afterPropertiesSet方法中调用buildSqlSessionFactory方法,buildSqlSessionFactory方法中使用XMLConfigBuilder解析属性configLocation中配置的路径,还会使用XMLMapperBuilder解析mapperLocations属性中的各个xml文件。
1、SqlSessionFactoryBean的部分源码
SqlSessionFactoryBean类:
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class);
private Resource configLocation;
private Configuration configuration;
private Resource[] mapperLocations;
//省略了其他属性和方法
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.dataSource, "Property 'dataSource' is required");
Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together");
this.sqlSessionFactory = this.buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
XMLConfigBuilder xmlConfigBuilder = null;
Configuration configuration;
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对象
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)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);
}
}
//省略了其他代码
if (!ObjectUtils.isEmpty(this.mapperLocations)) {
Resource[] var29 = this.mapperLocations;
var27 = var29.length;
for(var5 = 0; var5 < var27; ++var5) {
//遍历mapperLocations文件
Resource mapperLocation = var29[var5];
if (mapperLocation != null) {
try {
//使用XMLMapperBuilder解析各个mapper.xml文件
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception var20) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var20);
} 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);
}
}
2、XMLConfigBuilder的部分源码
XMLConfigBuilder解析mybatis中configLocation属性中的全局xml文件。
XMLConfigBuilder类的parse方法,解析mybatis的核心配置文件:
public Configuration parse() {
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
//解析mybatis核心配置文件,<property name="configLocation" value="classpath:mybatisConfig.xml"/>
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}
private void parseConfiguration(XNode root) {
try {
this.propertiesElement(root.evalNode("properties")); //解析<properties>标签
Properties settings = this.settingsAsProperties(root.evalNode("settings")); //解析<settings>标签
this.loadCustomVfs(settings);
this.typeAliasesElement(root.evalNode("typeAliases")); //解析<typeAliases>标签
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
this.settingsElement(settings);
this.environmentsElement(root.evalNode("environments")); //解析<environments>标签
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlerElement(root.evalNode("typeHandlers")); //解析<typeHandlers>标签
this.mapperElement(root.evalNode("mappers")); //解析<mappers>标签,即解析mapper.xml文件
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
Iterator var2 = parent.getChildren().iterator();
while(true) {
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String resource;
if ("package".equals(child.getName())) {
resource = child.getStringAttribute("name");
this.configuration.addMappers(resource);
} else {
resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
XMLMapperBuilder mapperParser;
InputStream inputStream;
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
inputStream = Resources.getResourceAsStream(resource);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
inputStream = Resources.getUrlAsStream(url);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
mapperParser.parse();
} else {
if (resource != null || url != null || mapperClass == null) {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
Class<?> mapperInterface = Resources.classForName(mapperClass);
this.configuration.addMapper(mapperInterface);
}
}
}
return;
}
}
}
由于XMLConfigBuilder内部也是使用XMLMapperBuilder,我们就看看XMLMapperBuilder的解析细节。
3、XMLMapperBuilder的部分源码
XMLMapperBuilder解析mybatis中mapperLocations属性中的mapper.xml文件。
public void parse() {
if (!this.configuration.isResourceLoaded(this.resource)) {
this.configurationElement(this.parser.evalNode("/mapper"));
this.configuration.addLoadedResource(this.resource);
this.bindMapperForNamespace();
}
this.parsePendingResultMaps();
this.parsePendingCacheRefs();
this.parsePendingStatements();
}
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace != null && !namespace.equals("")) {
this.builderAssistant.setCurrentNamespace(namespace);
this.cacheRefElement(context.evalNode("cache-ref"));
this.cacheElement(context.evalNode("cache"));
//解析mapper.xml中的parameterMap节点
this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析mapper.xml中的resultMap节点
this.resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析mapper.xml中的sql节点
this.sqlElement(context.evalNodes("/mapper/sql"));
//解析mapper.xml中的select|insert|update|delete节点
this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} else {
throw new BuilderException("Mapper's namespace cannot be empty");
}
} catch (Exception var3) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + var3, var3);
}
}
private void buildStatementFromContext(List<XNode> list) {
if (this.configuration.getDatabaseId() != null) {
this.buildStatementFromContext(list, this.configuration.getDatabaseId());
}
this.buildStatementFromContext(list, (String)null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
Iterator var3 = list.iterator();
while(var3.hasNext()) {
XNode context = (XNode)var3.next();
//使用XMLStatementBuilder解析select|insert|update|delete节点
XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException var7) {
this.configuration.addIncompleteStatement(statementParser);
}
}
}
4、XMLStatementBuilder的部分源码
XMLStatementBuilder解析mapper.xml文件中的select,insert,update,delete节点。
public void parseStatementNode() {
//获取<select>、<insert>、<update>、<delete>标签中的id属性,即mapper接口中的方法名称
String id = this.context.getStringAttribute("id");
String databaseId = this.context.getStringAttribute("databaseId");
if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
Integer fetchSize = this.context.getIntAttribute("fetchSize");
Integer timeout = this.context.getIntAttribute("timeout");
//获取<select>、<insert>、<update>、<delete>标签中的parameterMap属性
String parameterMap = this.context.getStringAttribute("parameterMap");
//获取<select>、<insert>、<update>、<delete>标签中的parameterType属性
String parameterType = this.context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = this.resolveClass(parameterType);
//获取<select>、<insert>、<update>、<delete>标签中的resultMap属性
String resultMap = this.context.getStringAttribute("resultMap");
//获取<select>、<insert>、<update>、<delete>标签中的resultType属性
String resultType = this.context.getStringAttribute("resultType");
String lang = this.context.getStringAttribute("lang");
LanguageDriver langDriver = this.getLanguageDriver(lang);
Class<?> resultTypeClass = this.resolveClass(resultType);
String resultSetType = this.context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(this.context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = this.resolveResultSetType(resultSetType);
String nodeName = this.context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = this.context.getBooleanAttribute("flushCache", !isSelect).booleanValue();
boolean useCache = this.context.getBooleanAttribute("useCache", isSelect).booleanValue();
boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", false).booleanValue();
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(this.configuration, this.builderAssistant);
includeParser.applyIncludes(this.context.getNode());
this.processSelectKeyNodes(id, parameterTypeClass, langDriver);
//XMLStatementBuilder中使用LanguageDriver解析SQL,并创建SqlSource
SqlSource sqlSource = langDriver.createSqlSource(this.configuration, this.context, parameterTypeClass);
String resultSets = this.context.getStringAttribute("resultSets");
String keyProperty = this.context.getStringAttribute("keyProperty");
String keyColumn = this.context.getStringAttribute("keyColumn");
String keyStatementId = id + "!selectKey";
keyStatementId = this.builderAssistant.applyCurrentNamespace(keyStatementId, true);
Object keyGenerator;
if (this.configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = this.configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = this.context.getBooleanAttribute("useGeneratedKeys", this.configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)).booleanValue() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
}
XMLLanguageDriver和RawLanguageDriver都继承了LanguageDriver类,由于在Configuration类的setDefaultScriptingLanguage方法中设置了XMLLanguageDriver,即默认使用XMLLanguageDriver类中的createSqlSource方法创建SqlSource。
public void setDefaultScriptingLanguage(Class<?> driver) {
if (driver == null) {
driver = XMLLanguageDriver.class;
}
this.getLanguageRegistry().setDefaultDriverClass(driver);
}
XMLLanguageDriver类中的createSqlSource方法如下所示:
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
5、XMLScriptBuilder的部分源码
XMLScriptBuilder解析xml中各个sql中的子节点,比如trim,if,where节点。
public class XMLScriptBuilder extends BaseBuilder {
private final XNode context;
private boolean isDynamic;
private final Class<?> parameterType;
public XMLScriptBuilder(Configuration configuration, XNode context) {
this(configuration, context, (Class)null);
}
public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
super(configuration);
this.context = context;
this.parameterType = parameterType;
}
//解析标签,并创建SQLSource
public SqlSource parseScriptNode() {
//解析SQL节点,获取节点列表
List<SqlNode> contents = this.parseDynamicTags(this.context);
//创建MixedSqlNode,MixedSqlNode实现了SqlNode接口
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
SqlSource sqlSource = null;
if (this.isDynamic) {
//如果节点是标签,就用DynamicSqlSource创建SqlSource
sqlSource = new DynamicSqlSource(this.configuration, rootSqlNode);
} else {
//如果节点是文本,就用RawSqlSource创建SqlSource
sqlSource = new RawSqlSource(this.configuration, rootSqlNode, this.parameterType);
}
//得到SqlSource,就可以得到BoundSql,BoundSql中含有sql,从而可以获取到sql语句
return (SqlSource)sqlSource;
}
//解析标签
List<SqlNode> 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));
String nodeName;
if (child.getNode().getNodeType() != 4 && child.getNode().getNodeType() != 3) {
if (child.getNode().getNodeType() == 1) {
nodeName = child.getNode().getNodeName();
XMLScriptBuilder.NodeHandler handler = this.nodeHandlers(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents);
this.isDynamic = true;
}
} else {
nodeName = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(nodeName);
//如果节点是动态的,即节点是标签,不是文本,将节点添加到节点列表contents中
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
this.isDynamic = true;
} else {
//如果节点是文本或CDATA,就创建StaticTextSqlNode,并将文本添加到contents中
contents.add(new StaticTextSqlNode(nodeName));
}
}
}
return contents;
}
XMLScriptBuilder.NodeHandler nodeHandlers(String nodeName) {
Map<String, XMLScriptBuilder.NodeHandler> map = new HashMap();
map.put("trim", new XMLScriptBuilder.TrimHandler());
map.put("where", new XMLScriptBuilder.WhereHandler());
map.put("set", new XMLScriptBuilder.SetHandler());
map.put("foreach", new XMLScriptBuilder.ForEachHandler());
map.put("if", new XMLScriptBuilder.IfHandler());
map.put("choose", new XMLScriptBuilder.ChooseHandler());
map.put("when", new XMLScriptBuilder.IfHandler());
map.put("otherwise", new XMLScriptBuilder.OtherwiseHandler());
map.put("bind", new XMLScriptBuilder.BindHandler());
return (XMLScriptBuilder.NodeHandler)map.get(nodeName);
}
//NodeHandler是XMLScriptBuilder类的内部接口,ChooseHandler、IfHandler、ForEachHandler、TrimHandler等都是XMLScriptBuilder的内部类,且都实现了NodeHandler接口
private interface NodeHandler {
void handleNode(XNode var1, List<SqlNode> var2);
}
//ForEachHandler实现了NodeHandler接口,重写了handleNode方法,对foreach节点(即<foreach>标签)进行解析
private class ForEachHandler implements XMLScriptBuilder.NodeHandler {
public ForEachHandler() {
}
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
List<SqlNode> contents = XMLScriptBuilder.this.parseDynamicTags(nodeToHandle);
MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
String collection = nodeToHandle.getStringAttribute("collection");
String item = nodeToHandle.getStringAttribute("item");
String index = nodeToHandle.getStringAttribute("index");
String open = nodeToHandle.getStringAttribute("open");
String close = nodeToHandle.getStringAttribute("close");
String separator = nodeToHandle.getStringAttribute("separator");
ForEachSqlNode forEachSqlNode = new ForEachSqlNode(XMLScriptBuilder.this.configuration, mixedSqlNode, collection, index, item, open, close, separator);
targetContents.add(forEachSqlNode);
}
}
}
6、SqlSource接口
SqlSource是Sql源接口,代表从xml文件或注解映射的sql内容,主要就是用于创建BoundSql,有实现类DynamicSqlSource(动态Sql源),StaticSqlSource(静态Sql源)等。
public interface SqlSource {
BoundSql getBoundSql(Object var1);
}
DynamicSqlSource和RawSqlSource都实现了SqlSource接口。
public class DynamicSqlSource implements SqlSource {
private final Configuration configuration;
private final SqlNode rootSqlNode;
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
}
public BoundSql getBoundSql(Object parameterObject) {
//根据SqlNode处理DynamicContext,在DynamicContext中存放了sql语句中的节点和参数
DynamicContext context = new DynamicContext(this.configuration, parameterObject);
this.rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(this.configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
Iterator var7 = context.getBindings().entrySet().iterator();
while(var7.hasNext()) {
Entry<String, Object> entry = (Entry)var7.next();
boundSql.setAdditionalParameter((String)entry.getKey(), entry.getValue());
}
return boundSql;
}
}
BoundSql类:
public class BoundSql {
private final String sql;
private final List<ParameterMapping> parameterMappings;
private final Object parameterObject;
private final Map<String, Object> additionalParameters;
private final MetaObject metaParameters;
public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.parameterObject = parameterObject;
this.additionalParameters = new HashMap();
this.metaParameters = configuration.newMetaObject(this.additionalParameters);
}
public String getSql() {
return this.sql;
}
public List<ParameterMapping> getParameterMappings() {
return this.parameterMappings;
}
public Object getParameterObject() {
return this.parameterObject;
}
public boolean hasAdditionalParameter(String name) {
String paramName = (new PropertyTokenizer(name)).getName();
return this.additionalParameters.containsKey(paramName);
}
public void setAdditionalParameter(String name, Object value) {
this.metaParameters.setValue(name, value);
}
public Object getAdditionalParameter(String name) {
return this.metaParameters.getValue(name);
}
}
7、SqlNode接口
TextSqlNode、StaticTextSqlNode、TrimSqlNode、MixedSqlNode等都实现了SqlNode接口。
public interface SqlNode {
//每个实现类都对apply方法进行了重写
boolean apply(DynamicContext var1);
}
MixedSqlNode实现类:
public class MixedSqlNode implements SqlNode {
//contents是SqlNode列表,包含了实现SqlNode接口的各个实现类
private final List<SqlNode> contents;
public MixedSqlNode(List<SqlNode> contents) {
this.contents = contents;
}
public boolean apply(DynamicContext context) {
Iterator var2 = this.contents.iterator();
while(var2.hasNext()) {
SqlNode sqlNode = (SqlNode)var2.next();
//调用具体某个实现类的apply方法
sqlNode.apply(context);
}
return true;
}
}
TextSqlNode实现类:
public class TextSqlNode implements SqlNode {
private final String text;
private final Pattern injectionFilter;
public TextSqlNode(String text) {
this(text, (Pattern)null);
}
public TextSqlNode(String text, Pattern injectionFilter) {
this.text = text;
this.injectionFilter = injectionFilter;
}
public boolean isDynamic() {
TextSqlNode.DynamicCheckerTokenParser checker = new TextSqlNode.DynamicCheckerTokenParser();
GenericTokenParser parser = this.createParser(checker);
parser.parse(this.text);
return checker.isDynamic();
}
//将解析的SQL文本添加到DynamicContext中
public boolean apply(DynamicContext context) {
GenericTokenParser parser = this.createParser(new TextSqlNode.BindingTokenParser(context, this.injectionFilter));
context.appendSql(parser.parse(this.text));
return true;
}
//省略了其他方法
}
由以上部分源码可知,XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、XMLScriptBuilder都实现了BaseBuilder接口。
BaseBuilder接口及其实现类,这些Builder的作用就是用于构造sql。
下面简单分析下其中4个Builder:
1、XMLConfigBuilder
解析mybatis中configLocation属性中的全局xml文件(即mybatis的核心配置文件,如上例中的configuration.xml文件),内部会使用XMLMapperBuilder解析各个xml文件。
2、XMLMapperBuilder
遍历mybatis中mapperLocations属性中的xml文件中每个节点的Builder(即mapper配置文件,如上例中的StudentMapper.xml文件),内部会使用XMLStatementBuilder处理xml中的每个节点。
3、XMLStatementBuilder
解析mapper.xml文件中各个节点,比如select,insert,update,delete节点(即<select>等标签),内部会使用XMLScriptBuilder处理节点的sql部分,遍历产生的数据会丢到Configuration的mappedStatements中。
4、XMLScriptBuilder
解析xml中各个sql中的子节点,比如trim,if,where节点(即<trim>、<if>等标签)。
8、案例分析
假设mapper.xml文件中有下面这段SQL配置,接下来分析mybatis是如何解析这段SQL的。
<update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User">
UPDATE users
<trim prefix="SET" prefixOverrides=",">
<if test="name != null and name != ''">
name = #{name}
</if>
<if test="age != null and age != ''">
, age = #{age}
</if>
<if test="birthday != null and birthday != ''">
, birthday = #{birthday}
</if>
</trim>
where id = #{id}
</update>
1、首先通过XMLStatementBuilder解析mapper.xml文件中各个select、insert、update、delete节点,如此处的update节点。得到该节点下的所有子节点,分别是下面的三个子节点:
(1)文本节点 \n UPDATE users \n
(2)trim子节点 ...
(3)文本节点 \n where id = #{id} \n
2、其次通过XMLScriptBuilder解析SQL中的各个子节点
(1)如果节点类型是文本或者CDATA,构造一个TextSqlNode或StaticTextSqlNode
(2)如果节点类型是元素,说明该update节点是个动态sql,然后会使用NodeHandler处理各个类型的子节点。这里的NodeHandler是XMLScriptBuilder的一个内部接口,其实现类包括TrimHandler、WhereHandler、SetHandler、IfHandler、ChooseHandler等。看类名也就明白了这个Handler的作用,比如我们分析的trim节点,对应的是TrimHandler;if节点,对应的是IfHandler...
子节点trim被TrimHandler处理,TrimHandler内部也使用parseDynamicTags方法解析节点
3、遇到子节点是元素的话,重复以上步骤。
//NodeHandler是XMLScriptBuilder类的内部接口,TrimHandler是XMLScriptBuilder的内部类,实现了NodeHandler接口
private class TrimHandler implements XMLScriptBuilder.NodeHandler {
public TrimHandler() {
}
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
List<SqlNode> contents = XMLScriptBuilder.this.parseDynamicTags(nodeToHandle);
MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
String prefix = nodeToHandle.getStringAttribute("prefix");
String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");
String suffix = nodeToHandle.getStringAttribute("suffix");
String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");
TrimSqlNode trim = new TrimSqlNode(XMLScriptBuilder.this.configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
targetContents.add(trim);
}
}
以上update SQL最终通过parseDynamicTags方法得到的SqlNode集合如下:
trim子节点:
XMLScriptBuilder的作用是解析sql中的子节点,如trim、if、where等节点,在XMLScriptBuilder类的parseScriptNode方法中构造了SqlSource。由于这个update SQL是个动态节点,因此构造出的SqlSource是DynamicSqlSource。
//解析标签,并创建SQLSource
public SqlSource parseScriptNode() {
List<SqlNode> contents = this.parseDynamicTags(this.context);
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
SqlSource sqlSource = null;
if (this.isDynamic) {
//如果节点是标签,就用DynamicSqlSource创建SqlSource
sqlSource = new DynamicSqlSource(this.configuration, rootSqlNode);
} else {
//如果节点是文本,就用RawSqlSource创建SqlSource
sqlSource = new RawSqlSource(this.configuration, rootSqlNode, this.parameterType);
}
//得到SqlSource,就可以得到BoundSql,BoundSql中含有sql,从而可以获取到sql语句
return (SqlSource)sqlSource;
}
由上面SqlSource接口的源码可知,在DynamicSqlSource内部构造sql,并通过BoundSql返回。因此,最终将mapper.xml文件中的<select>、<insert>、<update>、<delete>节点中的由节点组成的SQL解析成一条完整的SQL语句,放到BoundSql中。然后再通过mybatis的预编译功能对解析后的SQL进行预编译,最后将预编译后的SQL语句交给数据库管理系统(DBMS)执行。