Mybatis方法--动态创建SQL版权声明
原创这篇文章将带您分析Mybatis是如何动态生成SQL。
首先,根据源代码分析框架进行初始化。xml文件加载、解析、缓存过程。亮点 xml解析过程 和 使用解析后的结果,最后列出示例并控制源代码。 DeBug 分析:当 DAO 标签的解析、参数的创建SQL并对整个过程进行总结。
- 数据处理
Mybatis对数据处理可以分为 使用动态参数进行装配sql 和 对sql执行的结果被封装。 JavaBean
这里包括两个进程: 1. 在查询阶段,我们将java数据类型,已转换jdbc数据类型, preparedStatement.setXXX() 来设值 2. 另一种观点是正确的。resultset查询结果集jdbcType 数据转换java 数据类型,本文仅介绍第一个过程。
- 基于传入参数的动态装配。sql
在 Mybatis 在,需要基于xml标签的语法编译了动力学。SQL,将在执行过程中根据标签进行解析,这里使用的是。 Ognl 解析动态构造的标签SQL语句
- 分析
parseDynamicTags解析过程:
Spring 与 Mybatis 整合需要配置 SqlSessionFactoryBean ,配置联接数据源并Mybatis xml配置文件路径和其他信息
其中 SqlSessionFactoryBean 实现了 Spring的InitializingBean 接口, InitializingBean 接口的 afterPropertiesSet 方法被调用 buildSqlSessionFactory 方法 该方法在内部使用。 XMLConfigBuilder 解析属性 configLocation 还将使用中配置的路径。 XMLMapperBuilder 属性解析 mapperLocations 每个属性xml文件在启动时将基于xml文件的配置路径已解析。xml文件,让我们看看加载时的一些源代码,并做简单的分析。读者可以关注一些带注释的代码:
XMLMapperBuilder:
/* 读者可以将注意力集中在 加注解 代码的一部分就足够了。 */
public class XMLMapperBuilder extends BaseBuilder {
public void parse() {
if(!this.configuration.isResourceLoaded(this.resource)) {
//根据xpath解析mapper节点
this.configurationElement(this.parser.evalNode("/mapper"));
this.configuration.addLoadedResource(this.resource);
this.bindMapperForNamespace();
}
this.parsePendingResultMaps();
this.parsePendingChacheRefs();
this.parsePendingStatements();
}
/**
* 根据xpath解析mapper节点
*/
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if(namespace.equals("")) {
throw new BuilderException("Mappers namespace cannot be empty");
} else {
//分配当前处理。mapper的namespace
this.builderAssistant.setCurrentNamespace(namespace);
//正在处理二级缓存
this.cacheRefElement(context.evalNode("cache-ref"));
this.cacheElement(context.evalNode("cache"));
//处理parameterMap节点
this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//处理resultMap节点
this.resultMapElements(context.evalNodes("/mapper/resultMap"));
//处理sql节点
this.sqlElement(context.evalNodes("/mapper/sql"));
//处理select|insert|update|delete节点
this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
}
} catch (Exception var3) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + var3, var3);
}
}
/**
* 处理select|insert|update|delete节点
*/
private void buildStatementFromContext(List list) {
if(this.configuration.getDatabaseId() != null) {
this.buildStatementFromContext(list, this.configuration.getDatabaseId());
}
this.buildStatementFromContext(list, (String)null);
}
/**
* 处理select|insert|update|delete节点
*/
private void buildStatementFromContext(List list, String requiredDatabaseId) {
Iterator i$ = list.iterator();
while(i$.hasNext()) {
XNode context = (XNode)i$.next();
//对于每个节点XMLStatementBuilder进行解析
XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);
try {
//解析每个节点
statementParser.parseStatementNode();
} catch (IncompleteElementException var7) {
this.configuration.addIncompleteStatement(statementParser);
}
}
}
}
XMLStatementBuilder :
public class XMLStatementBuilder extends BaseBuilder {
/**
* 解析节点
*/
public void parseStatementNode() {
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");
String parameterMap = this.context.getStringAttribute("parameterMap");
String parameterType = this.context.getStringAttribute("parameterType");
Class> parameterTypeClass = this.resolveClass(parameterType);
String resultMap = this.context.getStringAttribute("resultMap");
String resultType = this.context.getStringAttribute("resultType");
String lang = this.context.getStringAttribute("lang");
//使用LanguageDriver进行解析SQL
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", Boolean.valueOf(!isSelect)).booleanValue();
boolean useCache = this.context.getBooleanAttribute("useCache", Boolean.valueOf(isSelect)).booleanValue();
boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", Boolean.valueOf(false)).booleanValue();
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(this.configuration, this.builderAssistant);
includeParser.applyIncludes(this.context.getNode());
this.processSelectKeyNodes(id, parameterTypeClass, langDriver);
//解析创建SQL
SqlSource sqlSource = langDriver.createSqlSource(this.configuration, this.context, parameterTypeClass);
//其他代码在此省略
}
}
将使用默认设置。XMLLanguageDriver创建SqlSource(Configuration在构造函数中设置)。
XMLLanguageDriver 创建 SqlSource :
public class XMLLanguageDriver implements LanguageDriver {
public SqlSource createSqlSource(Configuration configuration, XNode script, Class> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
//使用XMLScriptBuilder的parseScriptNode该方法解析节点的SQL部分
return builder.parseScriptNode();
}
}
XMLScriptBuilder解析:
public class XMLScriptBuilder extends BaseBuilder {
public SqlSource parseScriptNode() {
//如果有子节点,则解析节点将递归调用解析
List contents = this.parseDynamicTags(this.context);
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
SqlSource sqlSource = null;
if(this.isDynamic) {
sqlSource = new DynamicSqlSource(this.configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(this.configuration, rootSqlNode, this.parameterType);
}
return (SqlSource)sqlSource;
}
}
XMLScriptBuilder 所有节点的递归解析:
public class XMLScriptBuilder extends BaseBuilder {
private XNode context;
private boolean isDynamic;
private Class> parameterType;
private Map nodeHandlers;
public XMLScriptBuilder(Configuration configuration, XNode context) {
this(configuration, context, (Class)null);
}
public XMLScriptBuilder(Configuration configuration, XNode context, Class> parameterType) {
super(configuration);
this.nodeHandlers = new HashMap() {
private static final long serialVersionUID = 7123056019193266281L;
{
//不同的标签有不同的解析类。
this.put("trim", XMLScriptBuilder.this.new TrimHandler(null));
this.put("where", XMLScriptBuilder.this.new WhereHandler(null));
this.put("set", XMLScriptBuilder.this.new SetHandler(null));
this.put("foreach", XMLScriptBuilder.this.new ForEachHandler(null));
this.put("if", XMLScriptBuilder.this.new IfHandler(null));
this.put("choose", XMLScriptBuilder.this.new ChooseHandler(null));
this.put("when", XMLScriptBuilder.this.new IfHandler(null));
this.put("otherwise", XMLScriptBuilder.this.new OtherwiseHandler(null));
this.put("bind", XMLScriptBuilder.this.new BindHandler(null));
}
};
this.context = context;
this.parameterType = parameterType;
}
/**
* 所有节点的递归解析
*/
private List parseDynamicTags(XNode node) {
List 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 = (XMLScriptBuilder.NodeHandler)this.nodeHandlers.get(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);
if(textSqlNode.isDynamic()) {
contents.add(textSqlNode);
this.isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(nodeName));
}
}
}
return contents;
}
/**
* 内部类IfHandler的实现
*/
private class IfHandler implements XMLScriptBuilder.NodeHandler {
private IfHandler() {
}
public void handleNode(XNode nodeToHandle, List targetContents) {
List contents = XMLScriptBuilder.this.parseDynamicTags(nodeToHandle);
MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
String test = nodeToHandle.getStringAttribute("test");
IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
targetContents.add(ifSqlNode);
}
}
//其他标签类型。handler不是一个接一个的例子,感兴趣的可以看到 XMLScriptBuilder 源代码实现
}
XMLConfigBuilder:解析mybatis中configLocation属性中的全局xml文件,该文件将在内部使用。 XMLMapperBuilder 解析各个xml文件。XMLMapperBuilder:遍历mybatis中mapperLocations属性中的xml文件中每个节点的Builder,比如user.xml,内部将使用 XMLStatementBuilder 处理xml中的每个节点。XMLStatementBuilder:解析xml文件中的每个节点,如select,insert,update,delete节点,内部将使用 XMLScriptBuilder 处理节点的sql部分,则将遍历生成的数据拖放到Configuration的mappedStatements中。XMLScriptBuilder:解析xml中的每个节点sql部分的Builder。
至此,mapper.xml该文件已被解析、加载和获取 SqlSource , SqlSource 将会放到 Configuration 中,有了 SqlSource ,在实施的时间上会有所依据。 SqlSource 获取 BoundSql 从而获得所需的 SQL , Configuration 可以被视为一个巨大的资源池,Mybatis框架执行所需的数据可以是 Configuration 中获取, Configuration 的源码为:
public class Configuration {
protected Environment environment;
protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled;
protected boolean mapUnderscoreToCamelCase;
protected boolean aggressiveLazyLoading;
protected boolean multipleResultSetsEnabled;
protected boolean useGeneratedKeys;
protected boolean useColumnLabel;
protected boolean cacheEnabled;
protected boolean callSettersOnNulls;
protected String logPrefix;
protected Class extends Log> logImpl;
protected LocalCacheScope localCacheScope;
protected JdbcType jdbcTypeForNull;
protected Set lazyLoadTriggerMethods;
protected Integer defaultStatementTimeout;
protected ExecutorType defaultExecutorType;
protected AutoMappingBehavior autoMappingBehavior;
protected Properties variables;
protected ObjectFactory objectFactory;
protected ObjectWrapperFactory objectWrapperFactory;
protected MapperRegistry mapperRegistry;
protected boolean lazyLoadingEnabled;
protected ProxyFactory proxyFactory;
protected String databaseId;
protected Class> configurationFactory;
protected final InterceptorChain interceptorChain;
protected final TypeHandlerRegistry typeHandlerRegistry;
protected final TypeAliasRegistry typeAliasRegistry;
protected final LanguageDriverRegistry languageRegistry;
protected final Map mappedStatements;
protected final Map caches;
protected final Map resultMaps;
protected final Map parameterMaps;
protected final Map keyGenerators;
protected final Set loadedResources;
protected final Map sqlFragments;
protected final Collection incompleteStatements;
protected final Collection incompleteCacheRefs;
protected final Collection incompleteResultMaps;
protected final Collection incompleteMethods;
protected final Map cacheRefMap;
//SomeMethod...
}
以下示例说明:
在我们使用的示例中:
//mapper接口的方法
schoolCustomerDao.selectBySome(1l, "2017-09-17","120706049");
此SQL使用 if 标签
在执行 schoolCustomerDao.selectBySome(1l, "2017-09-17","120706049"); 时,
mapper代理类将首先确定此方法是否存在于缓存中,如果不存在则需要加载,如果已存在则直接调用,然后根据 select|insert|update|delete 呼叫类型不同。 SqlSession 方法,该方法基于调用前的条目。 (1l, "2017-09-17","120706049") 套餐参数,套餐参数源码如下:
//MapperMethod该类执行此方法来组合参数。
param = this.method.convertArgsToSqlCommandParam(args);
/**
* 拼装入参
*/
public Object convertArgsToSqlCommandParam(Object[] args) {
int paramCount = this.params.size();
if(args != null && paramCount != 0) {
if(!this.hasNamedParameters && paramCount == 1) {
return args[((Integer)this.params.keySet().iterator().next()).intValue()];
} else {
Map param = new MapperMethod.ParamMap();
int i = 0;
//召集参赛者key value 格式,并使用param+数字作为key对value按条目顺序排序
for(Iterator i$ = this.params.entrySet().iterator(); i$.hasNext(); ++i) {
Entry entry = (Entry)i$.next();
param.put(entry.getValue(), args[((Integer)entry.getKey()).intValue()]);
String genericParamName = "param" + String.valueOf(i + 1);
if(!param.containsKey(genericParamName)) {
param.put(genericParamName, args[((Integer)entry.getKey()).intValue()]);
}
}
return param;
}
} else {
return null;
}
}
在封装参数时,不仅会封装条目参数,还会按照条目顺序进行封装,带有 param + 数字 作为key,输入value 放入 Map 中,如下所示:

封装参数后的结果
在封装参数之后, 该方法的全名也是 StatementId (在本例中: com.school.dao.SchoolCustomerDao.selectBySome )和传入的封装参数:
//调用查询 this.command.getName() 为 com.school.dao.SchoolCustomerDao.selectBySome
result = sqlSession.selectOne(this.command.getName(), param);
此步骤已准备好开始解析和生成SQL了,
先取出 Configuration 中的 MappedStatement ,根据大会的参与度 SQL 再执行
//从configuration中获取MappedStatement
//statement 为 com.school.dao.SchoolCustomerDao.selectBySome
MappedStatement ms = this.configuration.getMappedStatement(statement);
//调用查询
List result = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
/**
* 调用查询
*/
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//将在此处生成部件。BoundSql
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
//执行查询
return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
getBoundSql 拼装的SQL代码为:
/**
* BoundSql boundSql = ms.getBoundSql(parameter);
* 调用以下方法类中的方法以获取。 BoundSql
*/
public BoundSql getBoundSql(Object parameterObject) {
//会有所不同,具体取决于sqlSource类型执行不同的解析,
//被叫到这里DynamicSqlSource并返回解析后的方法。BoundSql,并已排序,需要替换的参数如下,下面将详细说明。
BoundSql boundSql = this.sqlSource.getBoundSql(parameterObject);
List parameterMappings = boundSql.getParameterMappings();
if(parameterMappings == null || parameterMappings.size() <= 0) {
boundSql = new BoundSql(this.configuration, boundSql.getSql(), this.parameterMap.getParameterMappings(), parameterObject);
}
Iterator i$ = boundSql.getParameterMappings().iterator();
while(i$.hasNext()) {
ParameterMapping pm = (ParameterMapping)i$.next();
String rmId = pm.getResultMapId();
if(rmId != null) {
ResultMap rm = this.configuration.getResultMap(rmId);
if(rm != null) {
this.hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
return boundSql;
}

替换SQL中的相应参数
//DynamicSqlSource类
public class DynamicSqlSource implements SqlSource {
private Configuration configuration;
private SqlNode rootSqlNode;
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
}
/**
* 解析SQL
*/
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(this.configuration, parameterObject);
//会根据 rootSqlNode 中每个节点的内容 MixedSqlNode 的 apply 方法,分析合并SQL
this.rootSqlNode.apply(context);
//用 SqlSourceBuilder 将SQL中的 #{} 换成 ?
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 i$ = context.getBindings().entrySet().iterator();
while(i$.hasNext()) {
Entry entry = (Entry)i$.next();
boundSql.setAdditionalParameter((String)entry.getKey(), entry.getValue());
}
return boundSql;
}
}

debug有关某些参数的信息
/**
* this.rootSqlNode.apply(context);
* 会调用MixedSqlNode类方法解析程序集。SQL
*/
public class MixedSqlNode implements SqlNode {
private List contents;
public MixedSqlNode(List contents) {
this.contents = contents;
}
public boolean apply(DynamicContext context) {
Iterator i$ = this.contents.iterator();
while(i$.hasNext()) {
SqlNode sqlNode = (SqlNode)i$.next();
//解析和汇编,调用下面的此处。 IfSqlNode 的 apply 方法
sqlNode.apply(context);
}
return true;
}
}
public class IfSqlNode implements SqlNode {
private ExpressionEvaluator evaluator;
private String test;
private SqlNode contents;
public IfSqlNode(SqlNode contents, String test) {
this.test = test;
this.contents = contents;
this.evaluator = new ExpressionEvaluator();
}
//被调用的 IfSqlNode 的 apply 方法
public boolean apply(DynamicContext context) {
//调用 ExpressionEvaluator 的 evaluateBoolean 方法
//确定执行结果是否为true
if(this.evaluator.evaluateBoolean(this.test, context.getBindings())) {
this.contents.apply(context);
return true;
} else {
return false;
}
}
}
public class ExpressionEvaluator {
public ExpressionEvaluator() {
}
//被调用的 ExpressionEvaluator 的 evaluateBoolean 方法
public boolean evaluateBoolean(String expression, Object parameterObject) {
//调用 Ognl 分析、实施细节不再精细梳理,有兴趣的读者可DeBug查看
Object value = OgnlCache.getValue(expression, parameterObject);
return value instanceof Boolean?((Boolean)value).booleanValue():(value instanceof Number?!(new BigDecimal(String.valueOf(value))).equals(BigDecimal.ZERO):value != null);
}
}

sqlNode.apply(context);这里使用了对该方法的调用。Ognl进行解析
解析完成后,将替换一个参数 ? 的SQL,只需在执行时调用preparedStatement.setXXX()将List
总结:
- Mybatis中
mapper.xml重新加载文件时,将解析该文件rootSqlNode节点- 调用mapper的DAO接口的方法被代理,参数被封装,并被传入。StatementId (方法全名限制)
- 根据StatementId获取到
rootSqlNode节点、循环调用MixedSqlNode方法,使用Ognl 分析的结果被组装并返回。- 使用
SqlSourceBuilder将#{} 内容解析,生成排序参数List<ParameterMapping> parameterMappings并将 #{} 替换成 ?- 最终调用JDBC中的
preparedStatement.setXXX()方法中,已排序的参数。List<ParameterMapping> parameterMappings中的参数将按顺序排列。?替换,您将获得一个完整的SQL
以上是“Mybatis原理--动态生成SQL如整篇内容有误,敬请更正,取长补短,共同进步。谢谢。
作者:ChinaXieShuai
链接:https://www.jianshu.com/p/c4ce04fc5627
资料来源:简讯
版权归作者所有。请联系作者以获得商业转载的授权,并注明非商业转载的来源。
版权声明
所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除
itfan123



