1. MyBatis 参数占位符:${}与#{}的深度解析
1.1 底层实现机制对比
在MyBatis中处理SQL语句时,${}和#{}是两种完全不同的参数替换方式。它们的核心区别在于预处理阶段:
- #{} 采用预编译机制,MyBatis会将其转换为JDBC的PreparedStatement参数占位符(即问号?),然后由数据库驱动进行类型安全处理。例如:
sql复制SELECT * FROM users WHERE id = #{userId}
实际执行时会变成:
sql复制SELECT * FROM users WHERE id = ?
参数值会通过setXxx()方法安全地注入。
- ${} 则是简单的字符串替换,MyBatis会直接将参数值拼接到SQL语句中。例如:
sql复制SELECT * FROM ${tableName} WHERE id = ${userId}
如果tableName="users",userId=1,最终SQL会变成:
sql复制SELECT * FROM users WHERE id = 1
关键提示:${}拼接的值如果是字符串类型,需要手动添加引号,如:'${value}'
1.2 类型处理差异
#{}的智能类型转换是其重要优势:
- 基本类型自动装箱:如果Java参数是int,而数据库字段是BIGINT,#{}会自动处理类型转换
- 日期格式化:Date类型参数会自动转换为数据库支持的格式
- 特殊字符转义:单引号等特殊字符会被正确处理,避免语法错误
而${}则完全不做任何处理,直接进行字符串替换。如果参数值包含SQL特殊字符(如单引号),会导致SQL语法错误。
1.3 安全性与性能考量
SQL注入风险实测案例:
java复制// 危险示例:使用${}
@Select("SELECT * FROM users WHERE username = '${username}'")
List<User> findByUsername(@Param("username") String username);
// 当传入 username = "admin' OR '1'='1" 时
// 最终SQL:SELECT * FROM users WHERE username = 'admin' OR '1'='1'
// 这将返回所有用户数据!
性能对比:
- 预编译语句(#{}):
- 第一次执行:需要编译SQL
- 后续执行:复用编译结果,只需传输参数值
- 普通语句(${}):
- 每次执行都需要完整编译SQL
在TPS为1000的系统中,使用#{}比${}可减少约30%的数据库CPU负载。
1.4 正确使用场景指南
必须使用${}的情况:
- 动态表名/列名:
sql复制SELECT * FROM ${dynamicTable} ORDER BY ${sortColumn}
- SQL关键字动态化:
sql复制SELECT * FROM products ORDER BY price ${sortDirection}
最佳实践建议:
- 所有参数值传递都用#{}
- 使用${}时:
- 避
