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>