#{}和${}

·  阅读 33

sql预编译

定义

sql 预编译指的是数据库驱动在发送 sql 语句和参数给 DBMS 以前对 sql 语句进行编译,这样 DBMS 执行 sql 时,就不须要从新编译。

为何须要预编译

JDBC 中使用对象 PreparedStatement 来抽象预编译语句,使用预编译

  1. 预编译阶段能够优化 sql 的执行
    预编译以后的 sql 多数状况下能够直接执行,DBMS 不须要再次编译,越复杂的sql,编译的复杂度将越大,预编译阶段能够合并屡次操做为一个操做。
  2. 预编译语句对象能够重复利用
    把一个 sql 预编译后产生的 PreparedStatement 对象缓存下来,下次对于同一个sql,能够直接使用这个缓存的 PreparedState 对象。

mybatis 默认状况下,将对全部的 sql 进行预编译。

mysql预编译源码解析

mysql 的预编译源码在 com.mysql.jdbc.ConnectionImpl 类中,以下:

public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
    synchronized(this.getConnectionMutex()) {
        this.checkClosed();
        com.mysql.jdbc.PreparedStatement pStmt = null;
        boolean canServerPrepare = true;
        String nativeSql = this.getProcessEscapeCodesForPrepStmts() ? this.nativeSQL(sql) : sql;
        if (this.useServerPreparedStmts && this.getEmulateUnsupportedPstmts()) {
            canServerPrepare = this.canHandleAsServerPreparedStatement(nativeSql);
        }

        if (this.useServerPreparedStmts && canServerPrepare) {
            if (this.getCachePreparedStatements()) {
                synchronized(this.serverSideStatementCache) {
                    pStmt = (com.mysql.jdbc.PreparedStatement)this.serverSideStatementCache.remove(new ConnectionImpl.CompoundCacheKey(this.database, sql));
                    if (pStmt != null) {
                        ((ServerPreparedStatement)pStmt).setClosed(false);
                        ((com.mysql.jdbc.PreparedStatement)pStmt).clearParameters();
                    }

                    if (pStmt == null) {
                        try {
                            pStmt = ServerPreparedStatement.getInstance(this.getMultiHostSafeProxy(), nativeSql, this.database, resultSetType, resultSetConcurrency);
                            if (sql.length() < this.getPreparedStatementCacheSqlLimit()) {
                                ((ServerPreparedStatement)pStmt).isCached = true;
                            }

                            ((com.mysql.jdbc.PreparedStatement)pStmt).setResultSetType(resultSetType);
                            ((com.mysql.jdbc.PreparedStatement)pStmt).setResultSetConcurrency(resultSetConcurrency);
                        } catch (SQLException var13) {
                            if (!this.getEmulateUnsupportedPstmts()) {
                                throw var13;
                            }

                            pStmt = (com.mysql.jdbc.PreparedStatement)this.clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);
                            if (sql.length() < this.getPreparedStatementCacheSqlLimit()) {
                                this.serverSideStatementCheckCache.put(sql, Boolean.FALSE);
                            }
                        }
                    }
                }
            } else {
                try {
                    pStmt = ServerPreparedStatement.getInstance(this.getMultiHostSafeProxy(), nativeSql, this.database, resultSetType, resultSetConcurrency);
                    ((com.mysql.jdbc.PreparedStatement)pStmt).setResultSetType(resultSetType);
                    ((com.mysql.jdbc.PreparedStatement)pStmt).setResultSetConcurrency(resultSetConcurrency);
                } catch (SQLException var12) {
                    if (!this.getEmulateUnsupportedPstmts()) {
                        throw var12;
                    }

                    pStmt = (com.mysql.jdbc.PreparedStatement)this.clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);
                }
            }
        } else {
            pStmt = (com.mysql.jdbc.PreparedStatement)this.clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);
        }

        return (PreparedStatement)pStmt;
    }
}
复制代码

流程图以下所示:

image.png

mybatis之sql动态解析以及预编译源码

mybatis sql 动态解析

mybatis 在调用 connection 进行 sql 预编译以前,会对sql语句进行动态解析,动态解析主要包含以下的功能:

  • 占位符的处理
  • 动态sql的处理
  • 参数类型校验

mybatis强大的动态SQL功能的具体实现就在此。

{}和${}

在上一篇文章我是解析到获取BoundSql,并且最后我是发出了一张BoundSql的结构 image.png

    方法:
    Peopel selectOne(@Param("name")String name, @Param("id")int id);
    配置文件:
    <select id='selectOne'>
       select * from test where 1 = 1 and id = ${id} and name = #{name}
    </select>
复制代码
复制代码

调用的方法与配置的sql语句。 在之前我们知道我们在配置文件写的sql语句最后是被解析成一个个的SqlNode,然后将一个个的SqlNode存储到SqlSourse对象中去。不清楚的可以看下我之前的文章。在我主页里面有关于解析MyBatis源码的文章。废话不多说了。继续上次的文章,juejin.cn/post/684490… 截屏2019-11-10上午11.36.28

这个DynamicContext是构建完整Sql语句的上下文。 this.rootSqlNode.apply(context);是将一个个的SqlNode拼接起来,底层也就是用了StringBuilder.append()方法拼接的。关于${}的解析也是在这个方法实现的。在包org.apache.ibatis.scripting.xmltags.TextSqlNode下有这样的一个方法。如下:

就是在解析我们的sql语句的时候如果碰到{}符号就用TextSqlNode来封装,所以在拼接时候也是如此。解析很简单就是首先找到这个SqlNode的{}位置,然后进行字符串替换。所以说{}是字符串替换也就是因为这样。然后获取到{}符号中的值,还记得之前我们在解析的时候方法参数的解析吗??方法参数会被解析好然后放到一个map中。所以此时我们得到${}中的值就是map中的key,通过这个key去拿到值,然后进行字符串替换即可。

解析完${}符号,接下来就是#{}了。

我们进入到这个parse()方法中

跟解析${}一样,但是#{}不是字符串替换,而是用 ? 代替。 我们知道方法参数 public void selectById(@Params("name")String name,@Params("id")int id) 会被ParamNameResolver解析成一个Map,Map中KEY值自然就是注解@Param中的值,VALUE就是我们传进来的值,在解析#{}占位符的时候,每一个#{}占位符都会被解析成一个ParameterMapping对象,ParameterMapping{property='name',mode=IN,javaType=class java.lang.String,jdbcType=null....},当然id也会解析成这个格式,然后将解析好的ParameterMapping存放到一个List中去,具体实现是在 ParameterMappingTokenHandle类中的handleToken方法实现的。我们知道List是个有序集合,所以在将❓替换成我们传的参数就会遍历这个list,然后与我们的参数一一对应起来。整个流程如下图 image.png

循环list得到每个ParameterMapping对象,然后得到该对象的property属性,最后从参数Map中获取到值,之前说过参数经过解析会存储在一个Map中的,在经过这一步之后就能得到一条完整的SQL语句了,select * from test where name = #{name} and id = #{id} 就会解析成 select * from test where name = "bjh" and id = 1。

所以在编写sql配置文件的时候如果使用${},那么如果是字符串类型要加上 '',不然会报错,尽量使用#{},防止sql注入。整个的解析流程就是这样,得到了完整的sql语句那么就执行就可以了。执行下次在写吧。

总结

值可以使用#{}替换,但如果是mysql表或者字段关键字不能使用 #{} (1)、mybatis能使用#{}的时候尽量使用#{} (2)、表名、order by的排序字段作为变量时,使用{}。 (3)、#不需要关注数据类型,mybatis实现自动数据类型转换;不做数据类型转换,需要自行判断数据类型 (4)、#是预编译的方式,$是直接拼接;

分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改