Mybatis3源码分析(11)-Sql解析执行-BoundSql的加载-1
来源:互联网 发布:sql server能卸载吗 编辑:程序博客网 时间:2024/06/05 14:39
整理完SqlSession和Executor的关系之后,接下来看看一条sql是怎么被解析执行的。
如下例:
public static void queryUser(SqlSessionFactory sqlSessionFactory){SqlSession sqlSession=sqlSessionFactory.openSession();try{Map<String,Object> param=new HashMap<>();param.put("userId", "21458594739");//sqlSession.selectList方法就是要详细分析的方法List<User> list=sqlSession.selectList("com.ashan.user.selectUserDetail", param);System.out.println(list);sqlSession.commit();}catch(Exception e){sqlSession.rollback();}finally{sqlSession.close();}}
<resultMap type="com.ashan.mybatis.User" id="detailUserResultMap"><constructor><idArg column="user_id" javaType="String"/><arg column="user_name"/></constructor><result property="password" column="user_pwd" /><result property="type" column="user_type" javaType="com.ashan.mybatis.UserType" typeHandler="com.ashan.mybatis.UserTypeHandler"/><result property="svcnum" column="svc_num" /> <association property="cust" javaType="com.ashan.mybatis.Cust"> <id property="id" column="cust_id"/><result property="custname" column="cust_name"/><result property="certNo" column="cert_no"/></association><collection property="accts" ofType="com.ashan.mybatis.Acct"><id property="id" column="acct_id" /><result property="payName" column="pay_name"/><result property="bankNo" column="bank_no"/></collection></resultMap><select id="selectUserDetail" resultMap="detailUserResultMap"><![CDATA[select user_id,user_name,user_type,cust_idfrom tf_f_user a where a.user_id=#${userId} ]]></select>
DefaultSqlSession.selectList方法
public <E> List<E> selectList(String statement, Object parameter) { //RowBounds表示查询的范围,一般在分页时用到 return this.selectList(statement, parameter, RowBounds.DEFAULT); } public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { //从Configuration获取一个MappedStatement配置 MappedStatement ms = configuration.getMappedStatement(statement); //直接调用executor.query()方法 List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); return result; } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }从上可以看到sqlSession.selectList方法非常简单,他是用executor来完成查询的。再看看BaseExecutor对查询的实现:
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { //获取一个BoundSql,这个BoundSql的获取过程就是本节要详细讨论的 BoundSql boundSql = ms.getBoundSql(parameter); CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }
BoundSql类定义
如下是BoundSql的源代码
/** * An actual SQL String got form an {@link SqlSource} after having processed any dynamic content. * The SQL may have SQL placeholders "?" and an list (ordered) of an parameter mappings * with the additional information for each parameter (at least the property name of the input object to read * the value from). * </br> * Can also have additional parameters that are created by the dynamic language (for loops, bind...). *//** * @author Clinton Begin */public class BoundSql { //经过处理的sql,这个sql已经可以被数据库执行了 private String sql; //sql中的参数映射,只是映射,没有包含实际的值 private List<ParameterMapping> parameterMappings; //客户端执行sql时传入的参数 private Object parameterObject; //暂时不讨论 private Map<String, Object> additionalParameters; //暂时不讨论 private 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<String, Object>(); this.metaParameters = configuration.newMetaObject(additionalParameters); } public String getSql() { return sql; } public List<ParameterMapping> getParameterMappings() { return parameterMappings; } public Object getParameterObject() { return parameterObject; } public boolean hasAdditionalParameter(String name) { return metaParameters.hasGetter(name); } public void setAdditionalParameter(String name, Object value) { metaParameters.setValue(name, value); } public Object getAdditionalParameter(String name) { return metaParameters.getValue(name); }}从源代码可以看出,BoundSql只是一个简单的java对象,有两个属性比较重要
- sql:从解析时可以看出这个sql不是配置文件中的sql,这个sql已经经过了处理(如:占用位符的处理、动态语句的解析if、foreach等待)
- parameterMappings:sql对应的参数列表
<![CDATA[select user_id,user_name,user_type,cust_idfrom tf_f_user a where a.user_id=#{userId} ]]><if test="userName!=null"> and user_name=#{userName} </if>
如果执行这条sql里参数中的userName属性为空,那么sql的值将会是
select user_id,user_name,user_type,cust_idfrom tf_f_user a where a.user_id=?parameterMappings.size()大小为1,只记录了userId这个参数映射
如果userName不为空,那么sql的值将会是
select user_id,user_name,user_type,cust_idfrom tf_f_user a where a.user_id=? and user_name=?parameterMappings.size()大小为2,记录了userId和userName两个参数映射
MappedStatement.getBoundSql()方法
public BoundSql getBoundSql(Object parameterObject) { //通过sqlSource对象获取 BoundSql boundSql = sqlSource.getBoundSql(parameterObject); //parameterMap一般不会配置,如下内容不讨论 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings == null || parameterMappings.size() <= 0) { boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject); } // check for nested result maps in parameter mappings (issue #30) for (ParameterMapping pm : boundSql.getParameterMappings()) { String rmId = pm.getResultMapId(); if (rmId != null) { ResultMap rm = configuration.getResultMap(rmId); if (rm != null) { hasNestedResultMaps |= rm.hasNestedResultMaps(); } } } return boundSql; }还记得sqlSource是怎么被创建的吗?(前面章节有详细说明)
public SqlSource parseScriptNode() { List<SqlNode> contents = parseDynamicTags(context); MixedSqlNode rootSqlNode = new MixedSqlNode(contents); SqlSource sqlSource = null; if (isDynamic) { sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource; }
DynamicSqlSource.getBoundSql()方法
public class DynamicSqlSource implements SqlSource { private Configuration configuration; private SqlNode rootSqlNode; public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) { this.configuration = configuration; this.rootSqlNode = rootSqlNode; } public BoundSql getBoundSql(Object parameterObject) { DynamicContext context = new DynamicContext(configuration, parameterObject); //sqlNode使用组合模式实现,他有多个SqlNode对象 //每个SqlNode的apply方法调用时,都为将sql加到context中,最终通过context.getSql()得到完整的sql rootSqlNode.apply(context); SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()); BoundSql boundSql = sqlSource.getBoundSql(parameterObject); for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) { boundSql.setAdditionalParameter(entry.getKey(), entry.getValue()); } return boundSql; }}DynamicContext可以看成是一个sql的容器,sqlNode的apply()方法会往这个容器上加sql.
DynamicContext动态上下文
这个类有两重要的属性
//参数上下文,ContextMap为一个Map private final ContextMap bindings; //sql,sqlNode中的apply()方法调用了appendSql(text)方法,最终会将sql保存在这个属性中 private final StringBuilder sqlBuilder = new StringBuilder();
public void appendSql(String sql) { sqlBuilder.append(sql); sqlBuilder.append(" "); } public String getSql() { return sqlBuilder.toString().trim(); }
再看看参数上下文
static class ContextMap extends HashMap<String, Object> { private static final long serialVersionUID = 2977601501966151582L; //这个对运行时的参数进行了包装 private MetaObject parameterMetaObject; public ContextMap(MetaObject parameterMetaObject) { this.parameterMetaObject = parameterMetaObject; } @Override public Object put(String key, Object value) { return super.put(key, value); } //这个方法才是最重要的 @Override public Object get(Object key) { String strKey = (String) key; //如果自身的map里 if (super.containsKey(strKey)) { return super.get(strKey); } if (parameterMetaObject != null) { //从参数里找 Object object = parameterMetaObject.getValue(strKey); // issue #61 do not modify the context when reading// if (object != null) { // super.put(strKey, object);// } return object; } return null; }这里举两个例子来说明ContextMap,其中MetaObject将在下一章节详细讨论
- 参数为Map类型
Map paraMap=new HashMap();paraMap.put("userId","12341234");paraMap.put("userName","ashan");List<User> list=sqlSession.selectList("dao.selectUser",paraMap);
- 参数为为一个普通的java对象
User user=new User();user.setUserId("12341234");user.setUserName("ashan");List<User> list=sqlSession.selectList("dao.selectUser",user);
SqlSource与SqlNode
下面详细分析apply()方法。
例如:DynamicSqlSource是从如下配置加载的
<![CDATA[select user_id,user_name,user_type,cust_idfrom tf_f_user a where a.user_id=#{userId} ]]><if test="userName!=null"> and user_name=${userName} </if>
结合例子说明一下sql在sqlNode中是怎么分布的
- StaticTextSqlNode1:保存了"select user_id,user_name,user_type,cust_id"
- StaticTextSqlNode2:保存了"from tf_f_user a"
- TextSqlNode3:保存了"where a.user_id=#{userId}",同时标识为动态的,因为他有占位符
- StaticTextSqlNode4:保存了"and"
- TextSqlNode5:保存了"user_name=#{userName}"
- IfSqlNode:保存了其test属性值,StaticTextSqlNode4和TextSqlNode5是否加入的context中也是由其控制的
StaticTextSqlNode.apply()方法
public boolean apply(DynamicContext context) { context.appendSql(text); return true; }只是简单的把对应的test追加到context中。
所以StaticTextSqlNode1和StaticTextSqlNode2的apply方法执行后,DynamicContext中的sql内容为:
select user_id,user_name,user_type,cust_id from tf_f_user a
TextSqlNode.apply()方法
public boolean apply(DynamicContext context) { //GenericTokenParser为一个占用符解析器 //BindingTokenParsery为一个TohenHandler:解析具体的占位符 GenericTokenParser parser = createParser(new BindingTokenParser(context)); context.appendSql(parser.parse(text)); return true; }
private GenericTokenParser createParser(TokenHandler handler) { //解析${tab_name}这种占位符,注意不是这种#{propertyName} return new GenericTokenParser("${", "}", handler); }再看看GenericTokenParser.parse()方法:
public String parse(String text) { StringBuilder builder = new StringBuilder(); if (text != null && text.length() > 0) { char[] src = text.toCharArray(); int offset = 0; int start = text.indexOf(openToken, offset); while (start > -1) { if (start > 0 && src[start - 1] == '\\') { // the variable is escaped. remove the backslash. builder.append(src, offset, start - 1).append(openToken); offset = start + openToken.length(); } else { int end = text.indexOf(closeToken, start); if (end == -1) { builder.append(src, offset, src.length - offset); offset = src.length; } else { builder.append(src, offset, start - offset); offset = start + openToken.length(); String content = new String(src, offset, end - offset); //关键是这句,调用了handler.handleToken()方法 builder.append(handler.handleToken(content)); offset = end + closeToken.length(); } } start = text.indexOf(openToken, offset); } if (offset < src.length) { builder.append(src, offset, src.length - offset); } } return builder.toString(); }认真分析上面的代码,最关键的是调用了handler.handleToken(content)方法
如果text为:select ${primary_key},${col_name} from ${tab_name),那么handler.handleToken()方法会被调用三次,分别为:
- handler.handleToken("primary_key")
- handler.handleToken("col_name")
- handler.handleToken("tab_name")
再来看BindingTokenParser.handleToken()方法
public String handleToken(String content) { Object parameter = context.getBindings().get("_parameter"); if (parameter == null) { context.getBindings().put("value", null); } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) { context.getBindings().put("value", parameter); } //从ContextMap中取出content对应的值返回 Object value = OgnlCache.getValue(content, context.getBindings()); return (value == null ? "" : String.valueOf(value)); // issue #274 return "" instead of "null" }从上面可以看到TextSqlNode.apply(),只会处理"${}"这种占位符,而不会处理这种占位符:"#{}"
所以当TextSqlNode3.apply()执行完成之后,DynamicContext中的sql内容为:
select user_id,user_name,user_type,cust_id from tf_f_user a where user_id=#{userId}
IfSqlNode.apply()方法
public boolean apply(DynamicContext context) { //动态执行test属性中表达式,如果返回true,才会执行对应的SqlNode.apply()方法 if (evaluator.evaluateBoolean(test, context.getBindings())) { contents.apply(context); return true; } return false;` }结合上例,当IfSqlNode.apply()方法执行后,有两种情况:
如果参数中的userName不为空的话,DynamicContext中的sql内容为:如果参数呻的userName为空的话,DynamicContext中的sql内容为:ForEachSqlNode和ChooseSqlNode的实现原理跟IfSqlNode实现差不多,这里不做讨论!
select user_id,user_name,user_type,cust_id from tf_f_user a where user_id=#{userId} and user_name=#{userName}
select user_id,user_name,user_type,cust_id from tf_f_user a where user_id=#{userId}
小结
SqlNode.apply()方法生成的sql也只是半成品,并没有处理"#{}"占位符!这个占位符的处理后续再分析。
0 0
- Mybatis3源码分析(11)-Sql解析执行-BoundSql的加载-1
- Mybatis3源码分析(13)-Sql解析执行-BoundSql的加载-2
- Mybatis3源码分析(17)-Sql解析执行-缓存的实现
- Mybatis3源码分析(12)-Sql解析执行-MetaObject
- Mybatis3源码分析(14)-Sql解析执行-StatementHandler
- Mybatis3源码分析(15)-Sql解析执行-Statement初始化和参数设置
- Mybatis3源码分析(16)-Sql解析执行-结果集映射(ResultSetHandler)
- MyBatis源码(六)之动态Sql解析运行阶段BoundSql
- Mybatis3源码分析(三):解析mapper的xml配置文件
- Mybatis3源码分析(三):解析mapper的xml配置文件
- Mybatis3源码分析(02)-加载Configuration-XMLConfigBuilder
- Mybatis3源码分析(07)-加载Configuration-总结
- Mybatis3源码分析(04)-加载Configuration-XMLMapperBuilder加载ResultMap
- Mybatis3源码分析(05)-加载Configuration-加载MappedStatement
- Mybatis3源码分析(06)-加载Configuration-缓存配置加载
- Mybatis3源码分析(08)-加载Configuration-使用到的设计模式
- Mybatis3源码分析(03)-加载Configuration-ResultMap说明
- Mybatis3源码分析(20)-Mapper实现-配置加载
- Android点击事件总结
- 可附带图片的圆形进度条
- WINDOWS 平台下会话重放库概览。
- 面试时这五种人不被录用
- Java线程Dump分析工具--jstack
- Mybatis3源码分析(11)-Sql解析执行-BoundSql的加载-1
- 那些年我们错过的响应式编程
- Android天天数钱游戏源码
- sangshizhizhan
- LaTeX中对矩阵加行属性名称和列属性名称
- 设置tomcat 内存空间大小
- 外部类可以自由访问内部类的private方法
- 容器技术基础 lxc
- 设计模式之状态模式