mybatis深入理解(一)之#与$区别以及sql预编译转载

原创
小哥 3年前 (2022-10-27) 阅读数 9 #大杂烩

mybatis 中使用 sqlMap 进行 sql 在查询时,经常需要动态传递参数,例如当我们需要根据用户的名称过滤用户时,sql 如下:

select * from user where name = "ruhua";

上述 sql 在,我们希望 name 后的参数 "ruhua" 它是动态的和可变的,即E。在不同的时间按不同的名称查询用户。在……里面 sqlMap 的 xml 该文件中使用了以下内容 sql 可以实现参数的动态传递 name:

select * from user where name = #{name};

或者

select * from user where name = ${name};

对于上面的查询案例,请使用。 #{ } 和 ${ } 结果是一样的,但在某些情况下,我们只能使用两种方法中的一种。

与 $


区别

动态 SQL 是 mybatis 的强大功能之一 ORM 这是该框架的一个重要原因。mybatis 在对 sql 在预编译语句之前, sql 对于动态分析,解析为 BoundSql 对象,此处也用于动态 SQL 已处理完毕。

在动态 SQL 解析阶段, #{ } 和 ${ } 届时将有不同的表演:

#{ } 合二为一 JDBC 预编译语句(prepared statement)参数标记。

例如,sqlMap 中如下的 sql 语句

select * from user where name = #{name};

解析为:

select * from user where name = ?;

一个 #{ } 被合二为一参数占位符 ?

而,

${ } 只是为了一个纯洁的 string 替换,动态中 SQL 解析阶段将是变量替换。

例如,sqlMap 中如下的 sql

select * from user where name = ${name};

当我们传递参数时 "ruhua" 时,上述 sql 决议案如下:

select * from user where name = "ruhua";

在预编译之前 SQL 该语句不再包含变量。 name 了。

总而言之, ${ } 变量的替换阶段是动态的。 SQL 解析阶段,而 #{ }变量的替换在中。 DBMS 中。

用法 tips

1、能使用 #{ } 使用这个地方 #{ }

首先,这是出于性能考虑,相同的预编译 sql 可以重复使用。

其次, ${ } 在预编译之前,它已被变量替换,这是存在的。 sql 注入问题 。例如,以下内容 sql,

select * from ${tableName} where name = #{name}

假设,我们的参数 tableName 为 user; delete user; -- ,那么 SQL 动态解析阶段之后,在预编译之前 sql 将变为

select * from user; delete user; -- where name = ?;

-- 后续语句将用作注释,不起作用,因此原始查询语句秘密包含DELETE表数据 SQL!

2,表名作为变量,必须使用。 ${ }

这是因为表名是一个字符串,使用。 sql 占位符用单引号替换字符串。 ,这会导致 sql 语法错误,例如:

select * from #{tableName} where name = #{name};

在预编译之后sql 变为:

select * from ? where name = ?;

假设我们传入一个参数 tableName = "user" , name = "ruhua",则在占位符替换为变量后,sql 语句变为

select * from user where name=ruhua;

上述 sql 语句中存在语法错误,表名不能用单引号引起来。 (请注意,反引号 ``是可能的)。

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 synchronized java.sql.PreparedStatement prepareStatement(String sql,
            int resultSetType, int resultSetConcurrency) throws SQLException {
        checkClosed();

        //
        // FIXME: Create warnings if cant create results of the given
        // type or concurrency
        //
        PreparedStatement pStmt = null;

        boolean canServerPrepare = true;

        // 不同的数据库系统配对。sql语法转换
        String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql): sql;

        // 确定是否可以进行服务器端预编译。
        if (this.useServerPreparedStmts && getEmulateUnsupportedPstmts()) {
            canServerPrepare = canHandleAsServerPreparedStatement(nativeSql);
        }

        // 如果服务器端预编译是可能的
        if (this.useServerPreparedStmts && canServerPrepare) {

            // 它是否已缓存PreparedStatement对象
            if (this.getCachePreparedStatements()) {
                synchronized (this.serverSideStatementCache) {

                    // 从缓存中获取缓存。PreparedStatement对象
                    pStmt = (com.mysql.jdbc.ServerPreparedStatement)this.serverSideStatementCache.remove(sql);

                    if (pStmt != null) {
                        // 当缓存中存在对象时,原始的 sqlStatement 执行参数清空等。
                        ((com.mysql.jdbc.ServerPreparedStatement)pStmt).setClosed(false);
                        pStmt.clearParameters();
                    }

                    if (pStmt == null) {
                        try {
                            // 如果缓存不存在,则调用服务器端。(数据库)预编译的
                            pStmt = ServerPreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql,
                                    this.database, resultSetType, resultSetConcurrency);
                            if (sql.length() < getPreparedStatementCacheSqlLimit()) {
                                ((com.mysql.jdbc.ServerPreparedStatement)pStmt).isCached = true;
                            }

                            // 设置退货类型和并发类型。
                            pStmt.setResultSetType(resultSetType);
                            pStmt.setResultSetConcurrency(resultSetConcurrency);
                        } catch (SQLException sqlEx) {
                            // Punt, if necessary
                            if (getEmulateUnsupportedPstmts()) {
                                pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);

                                if (sql.length() < getPreparedStatementCacheSqlLimit()) {
                                    this.serverSideStatementCheckCache.put(sql, Boolean.FALSE);
                                }
                            } else {
                                throw sqlEx;
                            }
                        }
                    }
                }
            } else {

                // 未启用缓存时,直接调用服务器端预编译的
                try {
                    pStmt = ServerPreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql,
                            this.database, resultSetType, resultSetConcurrency);

                    pStmt.setResultSetType(resultSetType);
                    pStmt.setResultSetConcurrency(resultSetConcurrency);
                } catch (SQLException sqlEx) {
                    // Punt, if necessary
                    if (getEmulateUnsupportedPstmts()) {
                        pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);
                    } else {
                        throw sqlEx;
                    }
                }
            }
        } else {
            // 调用服务器端预编译时不支持客户端预编译(不需要数据库) connection )
            pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);
        }

        return pStmt;
    }

流程图如下:

mybatis之sql动态解析和预编译源代码

mybatis sql 动态解析

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

  • 占位符的处理

  • 动态sql的处理

  • 参数类型检查

mybatis强大的动力SQL下面是该函数的具体实现。动态解析涉及的事情太多,不能在后面讨论。

总结

本文主要对此进行了深入的探讨。 mybatis 对 #{ } 和 ${ }不同的治疗方法,并了解 sql 预编译。

版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除