Mybatis是如何进行分页的?分页插件的原理是什么?
Mybatis是如何进行分页的?
Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页,可以在sql内直接拼写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页,比如:MySQL数据的时候,在原有SQL后面拼写limit。 使用 RowBounds 进行分页,非常方便,不需要在 sql 语句中写 limit,即可完成分页功能。但是由于它是在 sql 查询出所有结果的基础上截取数据的,所以在数据量大的sql中并不适用,它更适合在返回数据结果较少的查询中使用
物理分页依赖的是某一物理实体,这个物理实体就是数据库,比如MySQL数据库提供了limit关键字,程序员只需要编写带有limit关键字的SQL语句,数据库返回的就是分页结果。
逻辑分页(内存分页)依赖的是程序员编写的代码。数据库返回的不是分页结果,而是全部数据,然后再由程序员通过代码获取分页数据,常用的操作是一次性从数据库中查询出全部数据并存储到List集合中,因为List集合有序,再根据索引获取指定范围的数据。
举个例子
@Mapper
public interface BookMapper {
//添加数据
int insert(Book book);
//模糊查询
List<Book> selectBookByName(Map<String, Object> map, RowBounds rowBounds);
}
<select id="selectBookByName" resultMap="BaseResultMap">
<bind name="pattern_bookName" value="'%' + bookName + '%'" />
<bind name="pattern_bookAuthor" value="'%' + bookAuthor + '%'" />
select * from book
where 1 = 1
<if test="bookName != null and bookName !=''">
and book_name LIKE #{pattern_bookName}
</if>
<if test="bookAuthor != null and bookAuthor !=''">
and book_author LIKE #{pattern_bookAuthor}
</if>
</select>
public List<Book> selectBookByName(String pageNo,String pageSize) {
//map是在service封装成的Map<String, Object> map,用来作为查询条件的,这里没有写出来
List<Book> list = bookMapper.selectBookByName(map, new RowBounds((pageNo-1)*pageSize, pageSize));
return list;
}
@RequestMapping("/selectBookByName")
@ResponseBody
public Page selectBookByName(String pageNo,String pageSize,HttpServletRequest request,HttpServletResponse response){
pageNo=pageNo==null?"1":pageNo; //当前页码
pageSize=pageSize==null?"5":pageSize; //页面大小
//获取当前页数据
List<Book> list = bookService.selectBookByName(pageNo,pageSize);
//获取总数据大小
//int totals = bookService.getAllBook();
//封装返回结果
Page page = new Page();
page.setTotal(totals+"");
page.setRows(list);
return page;
}
分页插件的原理是什么?
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数(注意这里是分页插件的原理不是Mybatis的分页原理)
举例:select * from student
拦截sql后重写为:select t.* from (select * from student)t limit 0,10
dialect方言
抛开数据库,生活中的方言是什么?方言就是某个地方的特色语言,是一种区别于其它地方的语言,只有你们这一小块地方能听懂,出了这个地方又是另一种方言。
数据库方言也是如此,MySQL 是一种方言,Oracle 也是一种方言,MSSQL 也是一种方言,他们之间在遵循 SQL 规范的前提下,都有各自的扩展特性。
拿分页来说,MySQL 的分页是用关键字 limit, 而 Oracle 用的是 ROWNUM,MSSQL 可能又是另一种分页方式。
# mysql
select * from t_user limit 10;
# oracle
select * from t_user t where ROWNUM <10;
这对于 ORM 框架来说,为了在上层的ORM层做了无差别调用,比如分页,对使用者来说,不管你底层用的是MySQL还是Oracle,他们用的都是一样的接口,但是底层需要根据你使用的数据库方言不同而调用不同的DBAPI。用户只需要在初始化的时候指定用哪种方言就好,其它的事情ORM框架帮你完成了

Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射方式?
1、都有哪些映射方式?
(1)当列名和封装查询结果的类的属性名一一对应时,这时MyBatis 有自动映射功能,将查询的记录封装到resultType 指定的类的对象中去
<mapper namespace="com.hanT.dao.UserDao">
<!--id对应接口中的方法名,resultType为返回结果的类型,要用类的全限定名-->
<select id="getUserList" resultType="com.hanT.pojo.User">
select * from mybatis.user
</select>
</mapper>
(2)当列名和封装查询结果的类的属性名不对应时
- 使用resultMap 标签,在标签中配置属性名和列名的映射关系
- 使用sql 列的别名功能,将列的别名书写为对象属性名,也可以做到映射关系
<resultMap type="cn.com.mybatis.pojo.User" id="UserResult">
<result property="username" column="name"/>
</resultMap>
<select id="findUserById" parameterType="java.lang.Long" resultMap="UserResult">
select id,name,email from t_user where id=#{id}
</select>
2、Mybatis是如何将sql执行结果封装为目标对象并返回的?
有了列名与属性名的映射关系后,Mybatis 通过反射创建对象(resultType有类的全路径),同时使用反射给对象的属性逐一赋值并返回, 那些找不到映射关系的属性,是无法完成赋值的。
Xml映射文件中,除了常见的select|insert|updae|delete 标签之外,还有哪些标签?
总的来说
<resultMap>、<sql>、<include>、<selectKey> ,加上动态 sql 的 9 个标签,其中 <sql> 为 sql 片段标签,通过<include> 标签引入 sql 片段, <selectKey> 为不支持自增的主键生成策略标签。
详细内容
- <resultMap/>标签,是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
- <sql/>标签,可被其他语句引用的可重用语句块。
- <include/>标签,引用标签的语句。
- <selectKey/>标签,不支持自增的主键生成策略标签。
九个动态SQL标签
<if/>、<choose/>、<when/>、<otherwise/>、<trim/>、<where/>、<set/>
<foreach/>、<bind/>
九个动态SQL标签详解:https://www.jb51.net/article/231807.htm
MyBatis实现一对一有几种方式?具体怎么操作的?
有联合查询和嵌套查询
联合查询是几个表联合查询,只查询一次,通过在 resultMap里面配置 association 节点配置一对一的类就可以完成;
嵌套查询是先查一个表,根据这个表里面的结果的外键id,去再另外一个表里面查询数据,也是通过 association 配置,但另外一个表的查询通过 select 属性配置
联合查询的具体举例
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jourwon.mybatis.mapper.ClassesMapper">
<!-- 一对一关联查询 -->
<select id="listClasses" parameterType="int" resultMap="ClassesResultMap">
select * from classes c,teacher t where c.teacher_id=t.t_id and c.c_id=#{id}
</select>
<resultMap type="com.jourwon.mybatis.pojo.Classes" id="ClassesResultMap">
<!-- 实体类的字段名和数据表的字段名映射 -->
<id property="id" column="c_id"/>
<result property="name" column="c_name"/>
<association property="teacher" javaType="com.jourwon.mybatis.pojo.Teacher">
<id property="id" column="t_id"/>
<result property="name" column="t_name"/>
</association>
</resultMap>
<!-- 一对多关联查询 -->
<select id="listClasses2" parameterType="int" resultMap="ClassesResultMap2">
select * from classes c,teacher t,student s where c.teacher_id=t.t_id and c.c_id=s.class_id and c.c_id=#{id}
</select>
<resultMap type="com.jourwon.mybatis.pojo.Classes" id="ClassesResultMap2">
<id property="id" column="c_id"/>
<result property="name" column="c_name"/>
<association property="teacher" javaType="com.jourwon.mybatis.pojo.Teacher">
<id property="id" column="t_id"/>
<result property="name" column="t_name"/>
</association>
<collection property="studentList" ofType="com.jourwon.mybatis.pojo.Student">
<id property="id" column="s_id"/>
<result property="name" column="s_name"/>
</collection>
</resultMap>
</mapper>
嵌套查询的具体举例
需求:查询一个订单,与此同时查询出该订单所属的用户
-- 先查询订单
Select * from orders;
-- 再根据订单oid外键,查询用户
select * from user where id = #{根据订单查询的oid}
用Mybatis来实现
// 1.查询所有的订单,与此同时还要查出每个订单所属的用户信息
List<Orders> findAllWithUser1();
<!--1.1 orderMap映射-->
<resultMap id="orderMap1" type="orders">
<id column="id" property="id"></id>
<result column="ordertime" property="ordertime"></result>
<result column="total" property="total"></result>
<result column="oid" property="oid"></result>
<association property="user" javaType="user" column="oid" select="cn.guardwhy.dao.UserMapper.findById"/>
</resultMap>
<!--1.2 一对一嵌套查询-->
<select id="findAllWithUser1" resultMap="orderMap1">
select * from orders
</select>
解析:<association> 标签用来实现一对一映射,也就是嵌套 Select 查询:通过执行另外一个 SQL 映射语句来加载期望的复杂类型。简单来说就是把上面原本的两句sql在mybatis中通过这个标签连接起来,其中select属性是嵌套调用UserMapper接口的findById方法来查询,这个方法的参数id从column属性中拿,相当于select * from user where id = #{oid}这句sql
// 根据ID查询用户
User findById(Integer id);
<!--1.1映射主键-->
<resultMap id="userMap1" type="user">
<id column="id" property="id"></id>
<result column="user_name" property="username"></result>
<result column="birthday" property="birthday"></result>
<result column="sex" property="sex"></result>
<result column="address" property="address"></result>
</resultMap>
<!--1.2 根据id查询用户-->
<select id="findById" resultMap="userMap1">
select * from user where id = #{oid}
</select>
@Test
public void testOrderWithUser(){
// 1.通过工具类得到会话对象
SqlSession sqlSession = MybatisUtils.getSession();
// 2.会话对象的得到mapper接口代理对象
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
// 3.调用方法
List<Orders> orders = mapper.findAllWithUser1();
// 4.遍历操作
for (Orders order : orders) {
System.out.println(order);
}
}
说说Mybatis的缓存机制

MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的 提升查询效率。 MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存) 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二 级缓存
一级缓存localCache
一级缓存也叫本地缓存(SqlSession级别的缓存): 与数据库同一次会话期间查询到的数据会放在本地缓存中。 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库

详细过程
每个 SqlSession 中持有了 Executor,每个 Executor 中有一个 LocalCache。当用户发起查询时,在 Local Cache 进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入 Local Cache,最后返回结果给用户。
一级缓存的特点
MyBatis 一级缓存的生命周期和 SqlSession 一致。
MyBatis 一级缓存内部设计简单,只是一个没有容量限定的 HashMap,在缓存的功能性上有所欠缺。
insert、update、delete(也就是下面的DML操作)语句会刷新缓存(刷新缓存就是更新缓存的数据为最新的数据库数据)
一级缓存默认开启,只在一次sqlsession中有效,也就是拿到连接的一个过程中有效
注意事项
一级缓存,在进行DML操作后,会使得缓存失效,也就是说Mybatis知道我们对数据库里面的数据进行了修改,所以之前缓存的内容可能就不是当前数据库里面最新的内容了。
还有一种情况就是,当前会话结束后,也会清理全部的缓存,因为已经不会再用到了。
注意:一个会话DML操作只会重置当前会话的缓存,不会重置其他会话的缓存,也就是说,其他会话缓存是不会更新的!一级缓存只针对于单个会话,多个会话之间不相通。
二级缓存
一级缓存给我们提供了很高速的访问效率,但是它的作用范围实在是有限,如果一个会话结束,那么之前的缓存就全部失效了,但是我们希望缓存能够扩展到所有会话都能使用,因此我们可以通过二级缓存来实现,二级缓存默认是关闭状态,要开启二级缓存,我们需要在映射器XML文件中添加
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
二级缓存是基于namespace级别(Mapper级别)的缓存,一个名称空间,对应一个二级缓存,不同的mapper查出的数据会放在自己对应的缓存(map)中,也就是说,当一个会话结束时,它的缓存依然会存在于二级缓存中,因此如果我们再次创建一个新的会话会直接使用之前的缓存。当第一个对话没有结束时,它的一级缓存就不会写入二级缓存中,也就是说,此时第二次对话就无法访问到之前的缓存
开启二级缓存后,会使用 CachingExecutor 装饰 Executor,进入一级缓存的查询流程前,先在 CachingExecutor 进行二级缓存的查询,具体的工作流程如下所示。

详细过程
一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中,如果当前会话关闭了,这个会话对应的一级缓存就没了,一级缓存中的数据会被保存到二级缓存中,新创建的会话就可以从二级缓存中获取内容,不同的mapper查出的数据会放在自己对应的缓存(map)中(也就是一个Mapper.xml文件对应一个二级缓存,也对应这个上面的一个namespace)
当开启缓存后,数据的查询执行的流程为: 二级缓存 -> 一级缓存 -> 数据库
注意事项
1. MyBatis 的二级缓存相对于一级缓存来说,实现了 SqlSession 之间缓存数据的共享,同时粒度 更加细,能够到 namespace 级别,通过 Cache 接口实现类不同的组合,对 Cache 的可控性 也更强。
2. MyBatis 在多表查询时,极大可能会出现脏数据
3. 由于默认的 MyBatis Cache 实现都是基于本地的,分布式环境下必然会出现 读取到脏数据,需要使用 Redis、Memcached 等分布式缓存
JDBC 编程有哪些步骤?
1、加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
2、通过驱动管理类获取数据库连接
成功加载后,会将Driver类的实例注册到DriverManager类中,而DriverManager就是驱动管理类。
Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test?characterEncoding=UTF-8", "root", "123456");
3、定义SQL语句,并用?表示占位符
String sql = "select * from user where username = ?";
4、获取预编译对象statement
PreparedStatement preparedStatement = connection.prepareStatement(sql);
5、设置参数
设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
preparedStatement.setString(1, "tom");
6、执行SQL
向数据库发出sql执行查询,查询出结果集
ResultSet resultSet = preparedStatement.executeQuery();
7、处理结果集
// 遍历查询结果集
while (resultSet.next()) {
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
// 封装User
user.setId(id);
user.setUsername(username);
System.out.println(user);
}
8、关闭连接
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
// 加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 通过驱动管理类获取数据库链接
connection =
DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?
characterEncoding=utf-8", "root", "root");
// 定义sql语句?表示占位符
String sql = "select * from user where username = ?";
// 获取预处理statement
preparedStatement = connection.prepareStatement(sql);
// 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
preparedStatement.setString(1, "tom");
// 向数据库发出sql执行查询,查询出结果集
resultSet = preparedStatement.executeQuery();
// 遍历查询结果集
while (resultSet.next()) {
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
// 封装User
user.setId(id);
user.setUsername(username);
System.out.println(user);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
MyBatis 中见过什么设计模式?
Mybatis至少遇到了以下的设计模式的使用
1.建造者模式,例如SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder;
2.工厂模式,例如SqlSessionFactory、ObjectFactory、MapperProxyFactory;
3.代理模式,Mybatis实现的核心,比如MapperProxy、ConnectionLogger,用的jdk的动态代理;还有executor.loader包使用了cglib或者javassist达到延迟加载的效果;
4.模板方法模式,例如BaseExecutor和SimpleExecutor,还有BaseTypeHandler和所有的子类例如IntegerTypeHandler;
5.适配器模式,例如Log的Mybatis接口和它对jdbc、log4j等各种日志框架的适配实现;
6.装饰者模式,例如Cache包中的cache.decorators子包中等各个装饰者的实现;
7.迭代器模式,例如迭代器模式PropertyTokenizer;
MyBatis 中比如 UserMapper.java 是接口,为什么没有实现类还能调用?
当我们调用UserMpper接口的任意方法时候,Mybatis会通过JDK动态代理生成一个动态代理对象(类型是$Proxy+数字,例如$Proxy6@2355),这个代理类会继承Proxy类(怕 g 喜),实现被代理的接口UserMapper,并且里面持有一个MapperProxy类型的触发管理类。当动态代理对象调用方法的时候会关联到一个InvocationHandler对象上,并且调用这个InvocationHandler对象,这里的这个InvocationHandler对象其实就是MapperProxy对象,然后调用MapperProxy的invoke方法(这个方法通常是用来执行某个目标对象的目标方法,但是这里的这个接口是没有实现类的,所以调用接口方法实质上调用的是MapperProxy的invoke方法),然后根据接口和方法名找到对应的映射文件(也就是UserMpper.xml)的对应方法的sql,最后执行sql即可。
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
//核心代码
public T newInstance(SqlSession sqlSession) {
//创建MapperProxy对象
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
//最终以JDK动态代理创建对象并返回
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
}
第一个newInstance方法是创建一个MapperProxy对象,这里注意MapperProxy对象不是JDK动态代理对象,第二个newInstance方法把这个MapperProxy对象对象作为第三个参数来创建JDK动态代理对象,这里面持有一个MapperProxy类型的触发管理类,可以理解为JDK动态代理对象里包含着MapperProxy对象,
//UserMapper 的类加载器
//接口是UserMapper
//h是mapperProxy对象
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h){
}
h:一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用,这里的h就是就是mapperProxy对象
mybatis sql语句里写 #和$区别?
mybatis sql语句里写 #和$区别?
#{}是预编译处理,是占位符,${}是字符串替换,是拼接符
Mybatis在处理#{}的时候会将sql中的#{}替换成?号,调用PreparedStatement来赋值,Mybatis在处理${}的时候就是把${}替换成变量的值,调用Statement来赋值
使用 #{} 可以有效的防止SQL注入,但是使用${}不可以防止SQL注入。
什么是SQL注入
SQL 注入就是在用户输入的字符串中加入 SQL 语句,如果在设计不良的程序中忽略了检查,那么这些注入进去的 SQL 语句就会被数据库服务器误认为是正常的 SQL 语句而运行,攻击者就可以执行额外的命令或访问未被授权的数据。
举个例子
我们有一个简单的查询操作:根据id查询一个用户信息。
它的sql语句应该是这样:select * from user where id = 。
我们根据传入条件填入id进行查询。
如果正常操作,传入一个正常的id,比如说2,那么这条语句变成
select * from user where id =2。
这条语句是可以正常运行并且符合我们预期的。
但是如果传入的参数变成'' or 1=1,这时这条语句变成select * from user where id = '' or 1=1。
它会将我们用户表中所有的数据查询出来,显然这是一个大的错误。这就是SQL注入。
也就是可以恶意的拼接sql语句来达到查询所有的数据的目的
#{} 方式
#{}: 解析为SQL时,会将形参变量的值取出,并自动给其添加双引号。 例如:当实参username="Amy"时,传入下Mapper映射文件后
<select id="findByName" parameterType="String" resultMap="studentResultMap">
SELECT * FROM user WHERE username=#{value}
</select>
SQL将解析为:
SELECT * FROM user WHERE username="Amy"
${} 方式
${}: 解析为SQL时,将形参变量的值直接取出,直接拼接显示在SQL中
例如:当实参username="Amy"时,传入下Mapper映射文件后
<select id="findByName" parameterType="String" resultMap="studentResultMap">
SELECT * FROM user WHERE username=${value}
</select>
SQL将解析如下:
SELECT * FROM user WHERE username=Amy
mybaits怎么防止SQL注入?
这个问题其实就是问#{}是能够防止sql注入是什么原理?
#{}方式是先用占位符代替参数(sql中的#{}替换为?号)将SQL语句先进行预编译,最后再将参数中的内容替换进来。由于SQL语句已经被预编译过,其SQL注入将无法通过非法的参数内容实现更改其参数中的内容,无法变为SQL命令的一部分,故#{}可以防止SQL注入。${}之所以不能防止SQL注入是因为它是直接把那个传入的值拼接在sql语句后面,这个时候如果恶意拼装sql就可以实现sql注入。
说白了就是#{}会把sql语句中的参数部分用?代替进行预编译(这个时候由于没有具体的参数所以叫预编译),最后把参数替换那个问号进行真正的sql操作,然后这个时候sql语句就已经固定了,所以你后面传的参数哪怕恶意拼装成了一个sql样式也没用,因为sql已经固定了,你传的任何参数在它眼里只是一个字符串,而不会当做一个sql语句去执行
更加底层的讲解
MyBatis防止SQL注入的原理:MyBatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值 ,PreparedStatement在执行阶段只是把输入串作为数据处理,不再对sql语句进行解析,准备,因此也就避免了sql注入问题。
PreparedStatement防止SQL注入的原理:JDBC的PreparedStatement会将带'?'占位符的sql语句预先编译好,也就是SQL引擎会预先进行语法分析,产生语法树,生成执行计划。对于占位符输入的参数,无论是什么,都不会影响该SQL语句的语法结构了,因为语法分析已经完成了,即使你后面输入了这些sql命令,也不会被当成sql命令来执行了,只会被当做字符串字面值参数。所以的sql语句预编译可以防御SQL注入。而且在多次执行同一个SQL时,能够提高效率。原因是SQL已编译好,再次执行时无需再编译。
Mybatis框架下易产生SQL注入漏洞的情况?
1、模糊查询
Select * from news where title like ‘%#{title}%’
在这种情况下使用#程序会报错,新手程序员就把#号改成了$,这样如果java代码层面没有对用户输入的内容做处理势必会产生SQL注入漏洞。
正确写法:
select * from news where tile like concat(‘%’,#{title}, ‘%’)
2、in 之后的多个参数
in之后多个id查询时使用# 同样会报错
Select * from news where id in (#{ids})
正确用法为使用foreach,而不是将#替换为$
id in
<foreach collection="ids" item="item" open="("separatosr="," close=")">#{ids} </foreach>
3、order by 之后
order by后面的条件用不了#{},只能用${}。所以只能在逻辑代码中手动防止sql注入。
不能用#{}是因为#{}会给参数内容自动加上单引号,会在有些需要表示字段名、表名的场景下,SQL将无法正常执行。现举一例说明:
期望查询结果按sex字段升序排列,参数String orderCol = "sex",mapper映射文件使用#{}:
<select id="findAddByName3" parameterType="String" resultMap="studentResultMap">
SELECT * FROM USER WHERE username LIKE '%Am%' ORDER BY #{value} ASC
</select>
则SQL解析及执行结果如下所示,很明显 ORDER 子句的字段名错误的被加上了引号,致使查询结果没有按期排序输出
SELECT * FROM USER WHERE username LIKE '%Am%' ORDER BY 'sex' ASC;
这时,现改为${}测试效果:
<select id="findAddByName3" parameterType="String" resultMap="studentResultMap">
SELECT * FROM USER WHERE username LIKE '%Am%' ORDER BY ${value} ASC
</select>