面试官:mybatis#{}和${}的区别? 您所在的位置:网站首页 ms和miss的区别知乎 面试官:mybatis#{}和${}的区别?

面试官:mybatis#{}和${}的区别?

2023-04-14 13:26| 来源: 网络整理| 查看: 265

#{}的原理

在MyBatis中,#{}属于预编译的参数,它会将传入的参数视为一个占位符,并将其转化为一个安全可控的sql语句。在执行sql之前,预先设置好了sql语句中的参数,再将参数传入sql语句中进行执行。

举个栗子:

@Select("SELECT * FROM user WHERE username = #{username} AND password=#{password}") User getUser(@Param("username") String username, @Param("password") String password); ${}的原理

{}的原理 {}属于字符串替换,它会将传入的参数直接拼接在sql语句中。因为它不进行预编译,所以存在SQL注入的风险。

比如:

@Select("SELECT * FROM user WHERE username = '${username}' AND password=${password}") User getUser(@Param("username") String username, @Param("password") String password);

这就会有sql注入的风险。

什么时候用${}

虽然${}有sql注入的危险,但还是有些情况需要我们使用${}。

当我们需要在SQL语句中传入表名或列名时,我们可以使用${},因为这个时候参数会作为一个字符串被拼接在sql语句中,并且这个参数是没有进行预编译的。如果我们使用#{},则会将传入的参数加入单引号。如果在SQL语句中传入字段名称或表名称,那么单引号就会产生问题。

举个栗子:

INSERT INTO ${tableName} (id,name,age) VALUES (#{id},#{name},#{age})

${tableName}是列名,如果你用#{}就会自动加上单引号,这样是不行的,只能用${}。

#{}和${}的使用区别

#{}的原理是在MyBatis源码中的处理过程中,将占位符替换成JDBC预编译语句中的“?”。例如在XML mapper文件中的SQL语句:

SELECT * FROM users WHERE id = #{id}

在实际执行时,MyBatis会将这个 SQL 中的 #{id} 替换成 ?,同时还会为预编译语句中的 ? 设置参数值。

而加上单引号这个做法则是因为如果不加单引号,一些类型的参数(比如字符串、日期等)在拼接SQL语句时会产生语法错误,因此MyBatis会自动在传入参数时加上单引号以避免这种错误。

例如,下面这个语句中的id参数可能是一个字符串类型:

SELECT * FROM users WHERE id = #{id}

在实际执行时,如果id参数的值是字符串类型的,则拼接出来的SQL语句就变成了:

SELECT * FROM users WHERE id = 'myExampleId' 如果没有单引号,就会变成:

SELECT * FROM users WHERE id = myExampleId 这段SQL语句会产生语法错误。

因此,MyBatis会自动在参数上加上单引号来避免这种错误。但也应该注意,不是所有的参数都需要加上单引号,比如数字类型的参数就不需要单引号。

#{}的源码实现

在 MyBatis 中,${}将参数直接拼接到 SQL 字符串中,而 #{}使用 PreparedStatement 的参数设置方式来实现。即在参数值传输到数据库驱动之前,Mybatis 的 SQL 解析引擎会将 SQL 中的 #{} 占位符替换为 ? ,然后调用 PreparedStatement 的 setXXX() 方法将实际的参数值传递到 SQL 中。从而避免 SQL 注入攻击的问题。

下面是 MyBatis#{}的实现过程,从执行器开始分析:

public class SimpleExecutor extends BaseExecutor { // 重载了父类的 query 非根方法 @Override protected List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { // 获得连接 & 创建 Statement 对象 stmt = createStatement(ms, parameter); // 执行 Statement ,返回结果集 ResultSet ResultSet rs = stmt.executeQuery(boundSql.getSql()); // 处理 ResultSet ,封装成 ResultHandler 等 return (List) resultHandler.handleResultSets(rs); } finally { // 关闭 Statement 对象 closeStatement(stmt); } } private Statement createStatement(MappedStatement ms, Object parameter) throws SQLException { Configuration configuration = ms.getConfiguration(); // 获得 DataSource Environment environment = configuration.getEnvironment(); DataSource dataSource = environment.getDataSource(); // 创建 Connection 对象 Connection connection = dataSource.getConnection(); // 创建 StatementHandler 对象 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 创建 Statement 对象 Statement stmt = null; // 准备 Statement 对象 stmt = handler.prepare(connection, transaction.getTimeout()); // 设置参数到 Statement 对象中 handler.parameterize(stmt); return stmt; } // ... }

可以看到,MyBatis会依次经过如下几个阶段执行:

1.创建 Statement 对象;

2.完成 Statement 对象的参数设置;

3.执行 Statement ,得到结果集 ResultSet ;

4.ResultHandler 处理结果集 ResultSet ,得到结果 。 其中,第2步完整的调用栈为:

public class MybatisParameterHandler implements ParameterHandler { // 重要代码 @Override public void setParameters(PreparedStatement ps) throws SQLException { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); List parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) { // 遍历 ParameterMapping 数组,并设置参数到 SQL 中 for (int i = 0; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); } typeHandler.setParameter(ps, i + 1, value, jdbcType); } } } } }

MyBatis会先遍历 ParameterMapping 数组,并设置参数到 SQL 中。在这个过程中,MyBatis 会先为 setObject() 方法传递参数的索引,然后根据 Java 类型,调用对应的 TypeHandler 完成参数设置。在这个过程中,MyBatis 还会判断: 当前的参数值是否为 null(null 要么用默认类型,要么用 parameterMapping 中指定的类型)、JDBC 类型是否为 null,并且还需要考虑 JDBC 类型和 Java 类型的映射问题。最终,MyBatis 会将参数设置出去,等待 PreparedStatement 对象执行 SQL语句。

总结

#{}进行预编译,${}进行字符串替换

#{}可避免SQL注入,${}存在SQL注入风险

当传入参数时,使用#{},当传入表名或列名时,使用${}。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有