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

6 篇文章 0 订阅
3 篇文章 0 订阅

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注入风险

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值