这篇文章是用来详细讲解MyBatis中#{}和${}的作用、区别以及使用场景。
1. 作用
#{}(PreparedStatement): 用于参数占位符,MyBatis会将#{}解析为?,然后使用PreparedStatement来设置参数值。这是一种预编译的方式。${}(Statement): 用于字符串替换,MyBatis会将${}直接替换为变量的值。这是一种字符串拼接的方式。
2. 区别
| 特性 | #{} (PreparedStatement) | ${} (Statement) |
|---|---|---|
| 安全性 | 高,防止SQL注入 | 低,易受SQL注入影响 |
| 预编译 | 是,预编译SQL语句 | 否,直接拼接SQL语句 |
| 数据类型 | 自动处理数据类型 | 字符串类型,需要手动处理 |
| 适用场景 | 大部分场景,参数传递 | 动态表名、排序字段等 |
| 性能 | 较高,可重用预编译语句 | 较低,每次都需要编译 |
详细解释:
- 安全性:
#{}使用预编译,会将参数值进行转义,防止恶意SQL注入。${}直接将变量值拼接到SQL语句中,如果变量值包含恶意代码,就可能导致SQL注入。 - 预编译:
#{}使用PreparedStatement,SQL语句在执行前会被预编译,数据库会缓存预编译后的语句,下次执行相同的语句时,可以直接使用缓存,提高性能。${}每次都需要重新编译SQL语句。 - 数据类型:
#{}会自动处理数据类型,例如将Java的Integer类型转换为数据库的INT类型。${}会将所有变量值都视为字符串类型,如果需要传递其他类型的数据,需要手动进行类型转换。 - 适用场景:
#{}适用于大部分场景,特别是需要传递参数值的情况。${}适用于动态表名、排序字段等不需要进行预编译的场景。 - 性能:
#{}由于预编译和语句缓存,性能通常比${}更高。
3. 使用场景
接下来我们用MyBatis-Plus来写代码示例,展示#{}和${}的使用场景。
注意: MyBatis-Plus 简化了 MyBatis 的很多操作,但底层仍然是 MyBatis,所以 #{} 和 ${} 的基本原理和区别仍然适用。
1. #{} 的使用场景 (MyBatis-Plus 风格)
-
传递参数值: 根据用户ID查询用户信息。
// 假设 UserMapper 继承了 BaseMapper<User> @Mapper public interface UserMapper extends BaseMapper<User> { @Select("SELECT * FROM users WHERE id = #{id}") User getUserById(@Param("id") int id); } // 使用 @Autowired private UserMapper userMapper; public void testGetUserById() { int userId = 123; User user = userMapper.getUserById(userId); System.out.println(user); }解释:
@Select注解直接定义了 SQL 语句。#{id}仍然是参数占位符,MyBatis-Plus 会自动处理。@Param("id")指定了参数名称,与#{id}对应。
-
插入数据:
// UserMapper 接口 (继承 BaseMapper<User>) @Mapper public interface UserMapper extends BaseMapper<User> { // 不需要额外注解,BaseMapper 提供了 insert 方法 } // 使用 @Autowired private UserMapper userMapper; public void testInsertUser() { User newUser = new User(); newUser.setUsername("testuser"); newUser.setEmail("test@example.com"); userMapper.insert(newUser); // MyBatis-Plus 提供的 insert 方法 }解释:
- MyBatis-Plus 的
BaseMapper提供了通用的insert方法,无需手动编写 SQL。 #{}在User对象的属性中自动匹配,将username和email的值插入到 SQL 语句中。
- MyBatis-Plus 的
-
更新数据:
// UserMapper 接口 (继承 BaseMapper<User>) @Mapper public interface UserMapper extends BaseMapper<User> { // 不需要额外注解,BaseMapper 提供了 updateById 方法 } // 使用 @Autowired private UserMapper userMapper; public void testUpdateUser() { User updatedUser = new User(); updatedUser.setId(123); // 必须设置 ID,否则无法更新 updatedUser.setUsername("newusername"); updatedUser.setEmail("newemail@example.com"); userMapper.updateById(updatedUser); // MyBatis-Plus 提供的 updateById 方法 }解释:
BaseMapper提供了updateById方法,根据 ID 更新数据。#{}在User对象的属性中自动匹配,将username和email的值更新到 SQL 语句中。
2. ${} 的使用场景 (MyBatis-Plus 风格)
-
动态表名: 根据不同的条件查询不同的表。
// UserMapper 接口 @Mapper public interface UserMapper extends BaseMapper<User> { @Select("SELECT * FROM ${tableName} WHERE id = #{id}") User getDataFromTable(@Param("tableName") String tableName, @Param("id") int id); } // 使用 @Autowired private UserMapper userMapper; public void testGetDataFromTable() { String tableName = "users"; // 确保 tableName 是安全的 int id = 123; User user = userMapper.getDataFromTable(tableName, id); System.out.println(user); }解释:
${tableName}直接将tableName的值拼接到 SQL 语句中。- 务必确保
tableName是安全的,防止 SQL 注入。
-
动态排序字段: 根据不同的字段进行排序。
// UserMapper 接口 @Mapper public interface UserMapper extends BaseMapper<User> { @Select("SELECT * FROM users ORDER BY ${sortField}") List<User> getAllUsers(@Param("sortField") String sortField); } // 使用 @Autowired private UserMapper userMapper; public void testGetAllUsers() { String sortField = "username"; // 确保 sortField 是安全的 List<User> users = userMapper.getAllUsers(sortField); System.out.println(users); }解释:
${sortField}直接将sortField的值拼接到 SQL 语句中。- 务必确保
sortField是安全的,防止 SQL 注入。
重要提示:
- 永远优先使用
#{},除非你明确知道${}是安全的并且是必要的。 - 在使用
${}时,务必对变量值进行严格的校验和过滤,防止SQL注入。 可以使用白名单机制,只允许特定的值通过。 - 避免将用户输入直接传递给
${},这几乎肯定会导致SQL注入。
总结:
#{}是安全的、预编译的参数占位符,适用于大部分场景。${}是字符串替换,适用于动态表名、排序字段等特殊场景,但需要非常小心SQL注入风险。 在实际开发中,应该尽量避免使用${},如果必须使用,一定要做好安全措施。