mybatis的sql参数化查询

  我们使用jdbc操作数据库的时候,都习惯性地使用参数化的sql与数据库交互。因为参数化的sql有两大有点,其一,防止sql注入;其二,提高sql的执行性能(同一个connection共用一个的sql编译结果)。下面我们就通过mybatis来分析一下参数化sql的过程,以及和非参数化sql的不同。

  注意:

    ①本次使用wireshark来监听网卡的请求,测试过程中,如果使用的是本地的mysql的话,java和mysql的交互是不需要经过wireshark的,所以如果是想用wireshark监听网卡的请求,推荐是链接远程的数据库。

    ②本文的项目源代码在文章末尾有链接(项目源代码中也有设计的表的sql)。

    ③可以结合wiereshark的抓包和mysql的general_log一起来查看sql的参数化过程,文章末尾会贴上从mysql的general_log角度检测到useServerPrepStmts=true/false两种执行方式的区别。

  

  一开始,项目中我的db配置如下,我们就先用这个配置来测试一下。

  

jdbc:mysql://xxx.xxx.xxx.xxx:3306/test?characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai

  mapper.xml如下

<?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="mapper.UserMapper">
  <select id="findByName" resultType="domain.User">      select *      from `user`      where user_id = #{name}  </select>
</mapper>

  测试用例如下:

public class UserMapperTest {

    @Test
    public void findByPk() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        try (SqlSession session = sqlSessionFactory.openSession()) {
            UserMapper mapper = session.getMapper(UserMapper.class);
            User user = mapper.findByName("SYS_APP_d5bwx8AfZXkKewMkOkaZhBv7MWqjiDX3qIkfPkG8");
            System.out.println(user);
        }
    }
}

  执行测试用例,通过wireshak监听请求,如下:

  从上图wireshark抓到的数据来看,执行查询并没有使用preparestatement,也不是参数化的sql,都是把拼装好参数的sql发送到mysql执行引擎去执行,为什么呢?经过查资料发现,db配置的url配置,漏了一个属性配置分别是useServerPrepStmts,修改后的db的url配置如下:

jdbc:mysql://xxx.xxx.xxx.xxx:3306/test?characterEncoding=utf-8&amp;useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;useServerPrepStmts=true

  增加了useServerPrepStmts属性配置之后,再来执行测试用例,看wireshark抓到的数据如下:

  (ps.如果useServerPrepStmts=true,是通过wireshark抓包结果可以看到,先是发送Request Prepare Statement--sql模板(同一connection第一次执行改sql模板才会发送,后面就不会在发送该Request),再发送Request Execute Statement--sql参数。而useServerPrepStmts=false的话,都是清一色的Request Query,其实就是没用到mysql server的预编译功能,所以是推荐配置useServerPrepStmts=true,提高参数化sql的执行性能)

  上图就是先发送待执行的sql模板(不带参数)到mysql服务端进行预编译,并且会在该请求的response中返回该sql编译之后的id,名曰:Statement ID,wireshark抓到的response数据如下:

  

  发送完sql模板之后,从response中拿到statement id之后,紧跟着就发送参数和statement id到mysql执行引擎,wireshark抓到的数据如下:

  如此,便可实现sql的参数化查询。按照理解,如果此时再用此sql模板查询另外一个user_id的数据,理论上是不需要再发送sql模板到mysql服务器了的,只需要发送参数和statement ID就可以了的,下面我就试一下,测试用例如下:

    @Test
    public void findByPk() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        try (SqlSession session = sqlSessionFactory.openSession()) {
            UserMapper mapper = session.getMapper(UserMapper.class);
            User user = mapper.findByName("SYS_APP_d5bwx8AfZXkKewMkOkaZhBv7MWqjiDX3qIkfPkG8");
            if (user != null || user == null) {
                user = mapper.findByName("SYS_APP_d5bwx8AfZXkKewMkOkaZhBv7MWqjiDX3qIkfPkG8");
            }
            System.out.println(user);
        }
    }

  执行测试用例,用wireshark抓包,如下:

  通过上图发现,怎么第二个findByName,压根就没发请求到mysql服务器,原来是因为本地的jdbc发现是相同的查询,直接返回了上一个查询的结果,所以不需要重新到mysql服务器去请求数据。那我在第二个findByName改一个和第一个不一样的参数,测试用例如下:

    @Test
    public void findByPk() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        try (SqlSession session = sqlSessionFactory.openSession()) {
            UserMapper mapper = session.getMapper(UserMapper.class);
            User user = mapper.findByName("SYS_APP_d5bwx8AfZXkKewMkOkaZhBv7MWqjiDX3qIkfPkG8");
            if (user != null || user == null) {
                user = mapper.findByName("SYS_APP_d5bwx8AfZXkKewMkOkaZhBv7MWqjiDX3qIkfPkG9");
            }
            System.out.println(user);
        }
    }

  执行上面这个测试用例,wireshark抓包结果如下:

  上图发现,竟然两次请求都重复发送了模板sql到mysql服务器预编译,为何呢?原来db的url配置里面还漏了一个属性配置(cachePrepStmts),增加cachePrepStmts配置之后,db的url配置如下:

jdbc:mysql://xxx.xxx.xxx.xxx:3306/test?characterEncoding=utf-8&amp;useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;useServerPrepStmts=true&amp;cachePrepStmts=true

  更新db的url配置之后,再执行测试用例,wireshark抓包结果如下:

  通过上图发现,第二次findByName不再发送模板sql了,直接就是发送Execute Statement了,其实Execute Statement就是执行sql的参数和statement ID(该connection第一次预编译模板sq的时候l返回的)。但是中间还是会发一个Reset Statement的mysql数据包,为什么要发这个Reset Statement数据包,有知道的同学,可以评论回复一下,我也还没去深究~谢谢~

  这里附带再说一下mybatis的参数化sql可以防止sql注入的理解,其实防止sql注入,有两点,其一,mybatis本身会有一个sql参数化的过程,这里涉及到mybatis的#和$的区别,参数化sql是用#引用变量,mybatis会对参数进行特殊字符以及敏感字符的转义以防止sql注入;其二,db的url配置中加了useServerPrepStmts=true之后,mysql服务端会对Execute Statement发送的参数中涉及的敏感字符进行转义,以防止sql注入,所以,如果不加useServerPrepStmts=true的话,会发现,mybatis在本地就已经对参数中涉及的敏感字符进行了转义之后,再发往mysql server,可以使用wireshark抓包看到;但是如果是加了useServerPrepStmts=true之后,会发现client发往mysql server的参数(Execute Statement),mybatis不会对其中的参数进行转义了,参数敏感字符转义这一块交给了mysql server去做,也可以通过wireshark抓包看到。so,这里会有两块地方防止sql注入,一块在client,一块在mysql server(使用存储过程防止sql注入也是使用了mysql server的该功能),就看你是否使用useServerPrepStmts。

附录:

 1.useServerPrepStmts=false/true,wireshark抓包结果

  useServerPrepStmts=false,wireshark抓包结果如下:

  

  useServerPrepStmts=true,wireshark抓包结果如下:

  

2. mysql server的general_log角度检测到useServerPrepStmts=false/true的执行sql

  useServerPrepStmts=false的general_log

  

2019-08-18T15:19:12.330744Z       38 Query    SET autocommit=0
2019-08-18T15:19:12.345704Z       38 Query    select *
        from `user`
        where user_id = ‘SYS_APP_d5bwx8AfZXkKewMkOkaZhBv7MWqjiDX3qIkfPkG8\‘ or 1 = 1 #‘
2019-08-18T15:19:12.358669Z       38 Query    select *
        from `user`
        where user_id = ‘SYS_APP_d5bwx8AfZXkKewMkOkaZhBv7MWqjiDX3qIkfPkG9‘
2019-08-18T15:19:12.359666Z       38 Query    SET autocommit=1

  useServerPrepStmts=true的general_log  

2019-08-18T09:39:42.533289Z       30 Query    SET autocommit=0
2019-08-18T09:39:42.546254Z       30 Prepare    select *
        from `user`
        where user_id = ?
2019-08-18T09:39:42.550244Z       30 Execute    select *
        from `user`
        where user_id = ‘SYS_APP_d5bwx8AfZXkKewMkOkaZhBv7MWqjiDX3qIkfPkG8\‘ or 1 = 1 #‘
2019-08-18T09:39:42.560217Z       30 Reset stmt
2019-08-18T09:39:42.561214Z       30 Execute    select *
        from `user`
        where user_id = ‘SYS_APP_d5bwx8AfZXkKewMkOkaZhBv7MWqjiDX3qIkfPkG9‘
2019-08-18T09:39:42.563210Z       30 Query    SET autocommit=1

原文地址:https://www.cnblogs.com/ismallboy/p/11374510.html

时间: 2024-10-08 03:33:55

mybatis的sql参数化查询的相关文章

8.mybatis动态SQL模糊查询 (多参数查询,使用parameterType)

多参数查询,使用parameterType.实例: 用户User[id, name, age] 1.mysql建表并插入数据 2.Java实体类 public class User { public User() { } public User(int id, String name, int age) { super(); this.id = id; this.name = name; this.age = age; } private int id; private String name;

SQL参数化查询

参数化查询(Parameterized Query 或 Parameterized Statement)是指在设计与数据库链接并访问数据时,在需要填入数值或数据的地方,使用参数 (Parameter) 来给值,这个方法目前已被视为最有效可预防SQL注入攻击 (SQL Injection) 的攻击手法的防御方式. 原理 在使用参数化查询的情况下,数据库服务器不会将参数的内容视为SQL指令的一部份来处理,而是在数据库完成 SQL 指令的编译后,才套用参数运行,因此就算参数中含有恶意的指令,由于已经编

SQL参数化查询的问题

最近碰到个问题, SQL语句中的 "... like '%@strKeyword%'"这样写查不出结果, 非的写成 "... like '%" + strKeyword + "%'"才能查出正确结果, 难道like子句不能用参数查询吗? 之前也碰到好多次, 当时都没在意, 这次刚好有空, 就研究了下, 发现并非like不能使用参数化查询, 而是之前的逻辑不对. like '%'[email protected]+'%'  --- 用+号表示字符串

mybatis中sql语句查询操作

动态sql where if where可以自动处理第一个and. <!-- 根据id查询用户信息 --> <!-- public User findUserById(int id); --> <select id="findUserById" parameterType="user" resultType="user"> select * from user <!-- 当有if条件成立时,where会自

【Mybatis】Mybatis的sql模糊查询

这个网站中有很多方法.https://code.google.com/p/mybatis/issues/detail?id=85 自己试验了如下的方法. 1.  参数中直接加入%% param.setUsername("%CD%");      param.setPassword("%11%"); <select id="selectPersons" resultType="person" parameterType=&

sql参数化查询in的参数

private Query setParameter(Query query, Map<String, Object> map) { if (map != null) { Set<String> keySet = map.keySet(); for (String string : keySet) { Object obj = map.get(string); //这里考虑传入的参数是什么类型,不同类型使用的方法不同 if(obj instanceof Collection<

参数化查询为什么能够防止SQL注入

很多人都知道SQL注入,也知道SQL参数化查询可以防止SQL注入,可为什么能防止注入却并不是很多人都知道的. 本文主要讲述的是这个问题,也许你在部分文章中看到过这块内容,当然了看看也无妨. 首先:我们要了解SQL收到一个指令后所做的事情: 具体细节可以查看文章:Sql Server 编译.重编译与执行计划重用原理 在这里,我简单的表示为: 收到指令 -> 编译SQL生成执行计划 ->选择执行计划 ->执行执行计划. 具体可能有点不一样,但大致的步骤如上所示. 接着我们来分析为什么拼接SQ

参数化查询为什么能够防止SQL注入 (转)

很多人都知道SQL注入,也知道SQL参数化查询可以防止SQL注入,可为什么能防止注入却并不是很多人都知道的. 本文主要讲述的是这个问题,也许你在部分文章中看到过这块内容,当然了看看也无妨. 首先:我们要了解SQL收到一个指令后所做的事情: 具体细节可以查看文章:Sql Server 编译.重编译与执行计划重用原理 在这里,我简单的表示为: 收到指令 -> 编译SQL生成执行计划 ->选择执行计划 ->执行执行计划. 具体可能有点不一样,但大致的步骤如上所示. 接着我们来分析为什么拼接SQ

为什么参数化查询可以防止SQL注入?(转)

昨天被某大牛问了一个问题,为什么SQL参数化查询可以防止SQL注入,参数化查询的原理是什么? 结果闷逼了,之前只知道参数化查询是可以防止SQL注入,但是没有深究其原理,今天就找一些文章,学习一下,也分享给大家. 以下引用知乎大神们的回答: 原理是采用了预编译的方法,先将SQL语句中可被客户端控制的参数集进行编译,生成对应的临时变量集,再使用对应的设置方法,为临时变量集里面的元素进行赋值,赋值函数setString(),会对传入的参数进行强制类型检查和安全检查,所以就避免了SQL注入的产生. 最近