mybatis中#{}和${}的区别

如果你不相信努力和时光,那么成果就会是第一个选择辜负你的。不要去否定你自己的过去,也不要用你的过去牵扯你现在的努力和对未来的展望。不是因为拥有希望你才去努力,而是去努力了,你才有可能看到希望的光芒。mybatis中#{}和${}的区别,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

mybatis中#{}和${}的区别?

#{}的原理

在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 id="insertData" parameterType="com.example.entity.Student">
   INSERT INTO 
   ${tableName}
   (id,name,age)
   VALUES
   (#{id},#{name},#{age})
</insert>

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

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

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

<select id="selectUserById" parameterType="int" resultType="com.example.User">
    SELECT * FROM users WHERE id = #{id}
</select>

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

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

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

<select id="selectUserById" parameterType="String" resultType="com.example.User">
    SELECT * FROM users WHERE id = #{id}
</select>

在实际执行时,如果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 <E> List<E> 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<E>) 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<ParameterMapping> 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注入风险

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/196228.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!