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

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

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 预编译。

版权声明

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