#{}是预编译处理,$ {}是字符串替换。
mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;mybatis在处理 $ { } 时,就是把 ${ } 替换成变量的值。
使用 #{} 可以有效的防止SQL注入,提高系统安全性。
${}
不能有效防止SQL注入,因为它只是简单的字符串替换,没有进行转义和处理,容易受到攻击者的注入攻击,下面是一个例子:
假设SQL语句为:
String sql = "SELECT * FROM users WHERE username = '${username}' AND password = '${password}'";
如果username和password参数中包含恶意的SQL语句(如' OR 1=1 #
),那么SQL注入攻击就会成功,从而导致查询到所有用户的信息。攻击者可以向程序传递如下参数:
String username = "' OR 1=1 #";
String password = "' OR 1=1 #";
经过简单的替换后,SQL语句变为:
String sql = "SELECT * FROM users WHERE username = '' OR 1=1 #' AND password = '' OR 1=1 #'";
此时,SQL语句的逻辑就被攻击者修改过了,由原来的查询指定用户,变为了查询所有用户。同时,#
号也被用来注释掉后面的内容,避免程序出现错误。
因此,在实际应用中,应该尽量使用#{}
占位符,避免使用${}
占位符,从而提高系统的安全性。
还有很多其他的标签, 、 、 、 、 ,加上动态 sql 的 9 个标签, trim|where|set|foreach|if|choose|when|otherwise|bind 等,其中 为 sql 片段标签,通过 标签引入 sql 片段, 为不支持自增的主键生成策略标签。
使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,而非物理分页;
Mybatis 中的 RowBounds 对象确实是对结果集执行的内存分页,而不是类似数据库的物理分页。因为 MyBatis 中的 RowBounds 仅仅是将查询 SQL 语句的返回结果集放在了一个 List 集合中,然后通过 List.subList(start, end) 的方式截取需要的分页数据,这样的方式存在一定的缺陷:
在 SQL 语句中直接写物理分页的参数虽然可以实现物理分页,但是需要自己手动编写 SQL 语句,在编写过程中容易出现分页参数的计算错误,同时也会让 SQL 语句变得较为冗长,不易维护。而且不同数据库的分页方法也有所不同,需要根据具体的数据库类型做出相应的调整。
分页插件可以更方便地实现物理分页功能。分页插件通常会拦截 MyBatis 执行的 SQL 语句,在其中自动添加分页相关的参数和语句,提供了统一的分页接口,支持多个数据库类型的分页功能,使用起来更加方便。同时,一些优秀的分页插件还可以对查询结果进行缓存、预处理等优化操作,进一步提升分页查询的效率和性能。
public interface UserMapper {
List<User> selectUsersWithRowBounds(RowBounds rowBounds);
}
<select id="selectUsersWithRowBounds" resultMap="userResultMap">
SELECT * FROM user ORDER BY id
select>
// 使用 RowBounds 对象实现分页查询
int pageNum = 2;
int pageSize = 10;
RowBounds rowBounds = new RowBounds(pageSize * (pageNum - 1), pageSize);
List<User> users = userMapper.selectUsersWithRowBounds(rowBounds);
<select id="selectUsersWithPhysicalPaging" resultMap="userResultMap">
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER(ORDER BY id) AS row_number FROM user
) AS t
WHERE t.row_number BETWEEN #{startRow} AND #{endRow}
select>
// 使用 SQL 语句实现物理分页查询
int pageNum = 2;
int pageSize = 10;
int startRow = pageSize * (pageNum - 1) + 1;
int endRow = pageSize * pageNum;
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("startRow", startRow);
paramMap.put("endRow", endRow);
List<User> users = userMapper.selectUsersWithPhysicalPaging(paramMap);
// 配置 MyBatis 分页插件
@Configuration
public class MyBatisConfig {
@Bean
public PageInterceptor pageHelper() {
PageInterceptor pageInterceptor = new PageInterceptor();
Properties properties = new Properties();
properties.setProperty("helperDialect", "mysql");
properties.setProperty("reasonable", "true");
properties.setProperty("supportMethodsArguments", "true");
pageInterceptor.setProperties(properties);
return pageInterceptor;
}
}
// 使用 MyBatis 分页插件实现物理分页查询
int pageNum = 2;
int pageSize = 10;
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectUsersWithPageHelper();
其中,PageHelper.startPage(pageNum, pageSize)
表示开启分页功能,将后续的查询操作进行物理分页。
分页插件的基本原理是使用 MyBatis 提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的 sql,然后重写 sql,根据 dialect 方言,添加对应的物理分页语句和物理分页参数。
MyBatis 动态 sql 可以让我们在 xml 映射文件内,以标签的形式编写动态 sql,完成逻辑判断和动态拼接 sql 的功能。
使用 OGNL(Object-Graph Navigation Language) 从 sql 参数对象中计算表达式的值,根据表达式的值动态拼接 sql,以此来完成动态 sql 的功能。
MyBatis 的动态 SQL 语句包括: