MyBatis面试题2

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: MyBatis面试题2

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>


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
6月前
|
缓存 Java 数据库连接
Mybatis缓存相关面试题有多卷
使用 MyBatis 缓存机制需要注意以下几点: 对于频繁更新和变动的数据,不适合使用缓存。 对于数据的一致性要求比较高的场景,不适合使用缓存。 如果配置了二级缓存,需要确保缓存的数据不会影响到其他业务模块的数据。 在使用缓存时,需要注意缓存的命中率和缓存的过期策略,避免缓存过期导致查询性能下降。
105 0
|
6月前
|
Java 关系型数据库 数据库连接
BATJ高频面试249道题:微服务+多线程+分布式+MyBatis +Spring
本文收集整理了各大厂常见面试题N道,你想要的这里都有内容涵盖:Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、Redis、MySQL、Spring、Spring Boot、Spring Cloud、RabbitMQ、Kafka、Linux 等技术栈,希望大家都能找到适合自己的公司,开开心心的撸代码。
|
5月前
|
SQL 缓存 Java
MyBatis最经典的20道面试题,你都会了吗?
MyBatis最经典的20道面试题,你都会了吗?
67 0
|
6月前
|
SQL 缓存 Java
Mybatis面试题
Mybatis面试题
|
4月前
|
SQL Java 数据库连接
Java面试题:简述ORM框架(如Hibernate、MyBatis)的工作原理及其优缺点。
Java面试题:简述ORM框架(如Hibernate、MyBatis)的工作原理及其优缺点。
70 0
|
6月前
|
存储 缓存 Java
什么!?实战项目竟然撞到阿里面试的原题!???关于MyBatis Plus的缓存机制
什么!?实战项目竟然撞到阿里面试的原题!???关于MyBatis Plus的缓存机制
|
6月前
|
SQL Java 数据库连接
MyBatis常见面试题总结2
MyBatis常见面试题总结
53 0
|
6月前
|
SQL Java 数据库连接
MyBatis常见面试题总结1
MyBatis常见面试题总结
62 0
|
6月前
|
SQL Java 数据库连接
答案很详细的MyBatis面试题(含示例代码)
MyBatis是一种优秀的持久层框架,它是一个轻量级的、优化的、功能强大的Java持久层框架,它的设计理念是贴近SQL、便于使用、高效并且功能丰富。通过MyBatis的使用,开发者能够更加专注于业务逻辑的实现,而不用过多关注底层的数据库操作。MyBatis通过XML或注解的方式配置SQL映射文件,并将Java的POJO(Plain Old Java Object,普通的Java对象)与数据库中的记录进行映射,使得开发人员能够以面向对象的方式来操作数据库,同时兼顾了SQL的灵活性和效率。灵活的SQL映射。
285 0
|
6月前
|
SQL Java 数据库连接
MyBatis常见面试题和答案(2020最新版)
MyBatis常见面试题和答案(2020最新版)
280 0
下一篇
无影云桌面