MyBatis_3

发布于:2025-07-27 ⋅ 阅读:(14) ⋅ 点赞:(0)

上一篇文章,我们学习了使用XML实现MyBatis进行增、删、查、改等操作,本篇文章,我们将学习#{ }和${ }获取方法参数的区别和使用MyBatisXML实现动态SQL语句。

#{ }和${ }的区别

在之前的文章中我们都是使用#{ }进行赋值,但实际上Mybatis也支持${ } 对参数进行赋值。

Integer类型的参数

我们先来看Integer类型的参数的赋值:

mapper:

  //Integer
    @Select("select * from userinfo where id = #{id} ")
    List<Userinfo> getById(Integer id);
    
    @Select("select * from userinfo where id = ${id} ")
    List<Userinfo> getById2(Integer id);

测试:

    @Test
    void getById() {
        System.out.println(mapper.getById(1));
    }

    @Test
    void getById2() {
        System.out.println(mapper.getById(1));
    }

#{ }: 

${ }: 

可以看到查询到的结果是一样的,都能够正常查到。此时,我们再换一个String类型试试。

String类型的参数

mapper:

   //String
    @Select("select * from userinfo where username = #{username} ")
    List<Userinfo> getByName(String username);

    @Select("select * from userinfo where username = ${username} ")
    List<Userinfo> getByName2(String username);

测试:

   @Test
    void getByName() {
        System.out.println(mapper.getByName("xmy"));
    }

    @Test
    void getByName2() {
        System.out.println(mapper.getByName2("xmy"));
    }

#{ }: 

测试通过。 

 ${ }:

可以看到报出BadSql异常。

此时观察日志,我们传过去的sql语句是这样的:通过观察,我们发现xmy处少了引号,正确的sql语句是‘xmy’。我们手动给sql语句加上引号,修改代码如下: 

    @Select("select * from userinfo where username = '${username}' ")
    List<Userinfo> getByName2(String username);

测试通过。

通过对比#{ }和${ }打出来的日志我们发现#{ }是预编译sql(通过?占位的方式,提前对sql进行编译然后把参数填充到SQL语句中),#{ }会根据参数类型自动拼接引号;而${ }则是直接进行字符替换,一起对SQL进行编译(即时sql)。如果参数为字符串,需要加上引号。 

在开发环境下,我们能使用#{ }就不要使用${ }。不仅仅因为#{ }的效率比${ }更高,而是因为${ }可能会产生sql注入的问题。

那么什么是sql注入呢?下面我们通过代码来演示sql注入。

${ }引发sql注入问题

sql注入:通过操作输入的数据来修改事先定义好的sql语句,以达到执行代码对服务器进行攻击的方法。

我们先尝试在数据库中使用下面的sql语句进行查询:

SELECT * FROM `userinfo` where username = ' OR 1 = '1;

 可以看到此时我们的代码是有问题的:

下面我们使用#{ }和${ } 分别进行查询:

  //String
    @Select("select * from userinfo where username = #{username} ")
    List<Userinfo> getByName(String username);

    @Select("select * from userinfo where username = '${username}' ")
    List<Userinfo> getByName2(String username);

 测试:

    @Test
    void getByName() {
        System.out.println(mapper.getByName("' OR 1 = '1"));
    }

    @Test
    void getByName2() {
        System.out.println(mapper.getByName2("' OR 1 = '1"));
    }

#{ }: 

${ }: 可以看到,${ }依然正常查询出来了,其中参数or被当作了SQL语句的一部分:

${ }的作用

从上面的例子中,我们可以知道:${}会有sql注入的风险,所以我们尽量使用#{}完成查询。

既然如此,${ }是不是就没有存在的必要了呢?

当然不是。接下来我们通过代码来看看${ }的作用。

1、使用${}实现排序功能

mapper: 

 @Select("select * from userinfo order by  id ${order}")
    List<Userinfo> getByOrder(String order);

测试: 

    @Test
    void getByOrder() {
      System.out.println(mapper.getByOrder("desc"));
    }

可以看到,能够根据id逆序输出结果。此时我们将${ }改成#{ } 。

    @Select("select * from userinfo order by id #{order}")
    List<Userinfo> getByOrder(String order);

测试结果: 

可以发现,当使用#{sort}查询时,desc前后加上了引号,导致sql错误。

2、使用${ }实现模糊查询

 mapper:

    @Select("select * from userinfo where username like '%${username}%'")
    List<Userinfo> getByLike(String username);

测试: 

 @Test
    void getByLike() {
        System.out.println(mapper.getByLike("zhangsan"));
    }

此时我们将${ } 改为#{ } :

 @Select("select * from userinfo where username like '%#{username}%'")
    List<Userinfo> getByLike(String username);

可以看到,依然是因为引号的关系,出现了异常。 

但是在模糊查询中使用#{ }也有解决办法 :我们可以使用concat()来拼接字符串:

  @Select("select * from userinfo where username like concat('%',#{username},'%')")
    List<Userinfo> getByLike1(String username);

测试: 

  @Test
    void getByLike1() {
        System.out.println(mapper.getByLike1("zhangsan"));
    }

总结: #{ }和${ }的区别

1、#{ }和${ }的区别就是预编译sql(占位)和即时sql(直接拼接)的区别。

2、#{ }使用预编译的形式所以性能会比${ }更高

绝大多数情况下,某一条sql语句可能会被反复调用执行,或者每次执行的时候只有个别的值不同(比如select的where子句值不同,insert的values值不同)。如果每次都需要上面语法解析,sql优化,sql编译等过程,效率就明显不行了。

预编译sql,编译一次之后会将编译后的sql语句缓存起来,后面再次执行这条语句时,不会再次编译(只是输入的参数不同),省去了解析优化等过程,以此来提高效率。

3、#{ }更安全(防止sql注入)

在使用${ }的场景下一定一定要考虑到sql注入问题,并采取措施进行防止:例如:1、在接口层(Controller层)进行判定,如果输入的结果不是我们想要的直接返回。2、直接给接口写死,根据用户输入的内容来决定调用哪个接口,如果没有接口符合,则返回。

4、在一些场景下仍然需要用到${ }

比如:排序,参数不需要引号…… 

数据库连接池

在MyBtis中,我们使用了数据库连接池技术,避免频繁地创建销毁连接。

数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有地数据库连接,而不是重新建立一个。 

没有使用数据库连接池的情况:每次执行sql语句,要先创建一个新的连接对象,然后执行sql语句,sql语句执行完,再关闭连接对象释放资源。这种重复的创建连接,销毁连接比较消耗资源。

使用数据库连接池的情况:程序启动时,会在数据库连接池中创建一定数量的Connection对象,当客户请求数据库连接池,会从数据库连接池中获取Connection对象,然后执行sql,sql语句执行完,再把Connection归还给连接池。

优点:

1、减少了网络开销

2、资源重用

3、提升了系统性能 

动态SQL

动态sql是mybaatis的强大特性之一,能够完成不同条件下不同的sql拼接。

官方文档:动态 SQL_MyBatis中文网

 <if>标签

在注册用户的时候,可能会有这样一个问题,如下图所示:

注册分为两种字段:必填字段和非必填字段,那如果在添加用户的时候有不确定的字段传入,程序应该如何实现呢?

这时候就需要使用动态标签进行判断了,如添加的时候性别为非必填字段,具体实现如下(XML实现): 

Mapper:

  Integer insertBatch(Userinfo userinfo);

XML:

    <insert id="insertBatch">
    insert into userinfo (username,password,age
                          <if test="gender!=null">
                              ,gender
                          </if>)
    values (#{username},#{password},#{age}
                            <if test="gender!=null">
                                ,#{gender}
                            </if>)
    </insert>

测试(有性别): 

    @Test
    void insertBatch() {
        Userinfo userinfo = new Userinfo();
        userinfo.setUsername("lisi");
        userinfo.setAge(16);
        userinfo.setPassword("lisi666");
        userinfo.setGender(2);
        mapper.insertBatch(userinfo);
    }

测试(无性别): 

    @Test
    void insertBatch() {
        Userinfo userinfo = new Userinfo();
        userinfo.setUsername("lisi");
        userinfo.setAge(16);
        userinfo.setPassword("lisi666");
        mapper.insertBatch(userinfo);
    }
 if标签详解:

<trim>标签

mapper:

Integer insertBatch2(Userinfo userinfo);

假如我们有许多的元素需要选填,那么我们此时的XML语句会变成这样:

    <insert id="insertBatch2">
        insert into userinfo(<if test="username != null">
                             username
                            </if>
                            <if test="password != null">
                             ,password
                            </if>
                            <if test="age != null">
                             ,age
                            </if>
                            <if test="gender != null">
                              ,gender  
                            </if>)values (<if test="username != null">
                               #{username}
                            </if>
                            <if test="password != null">
                               ,#{password}
                            </if>
                            <if test="age != null">
                                ,#{age}
                            </if>
                            <if test="gender != null">
                                ,#{gender}
                            </if>)
                            </insert>

我们测试的时候选填其中两个参数(性别,年龄): 

 @Test
    void insertBatch2() {
        Userinfo userinfo = new Userinfo();
        userinfo.setGender(1);
        userinfo.setAge(16);
        mapper.insertBatch2(userinfo);
    }

 测试不通过,观察报错日志发现是因为<if>标签的原因,导致我们sql语句多加了个逗号。

那么我们能不能把逗号加在后面呢?同样的,如果将逗号加在后面,那么后面也会多一个逗号。

下面我们使用<trim>标签来解决问题:

 <insert id="insertBatch2">
        insert into userinfo
            <trim prefix="(" suffix=")" prefixOverrides=",">
                             <if test="username != null">
                             username
                            </if>
                            <if test="password != null">
                             ,password
                            </if>
                            <if test="age != null">
                             ,age
                            </if>
                            <if test="gender != null">
                              ,gender
                            </if>
                            </trim>
                             values <trim prefix="(" suffix=")" prefixOverrides=","> 
                            <if test="username != null">
                               #{username}
                            </if>
                            <if test="password != null">
                               ,#{password}
                            </if>
                            <if test="age != null">
                                ,#{age}
                            </if>
                            <if test="gender != null">
                                ,#{gender}
                            </if>
                            </trim>
                            </insert>

 测试通过:

那么<trim>标签的作用是什么呢? 

<trim>标签详解:

<where>标签

我们在淘宝上逛东西时,通常会有一些按钮能够动态组装我们的查询条件。

这种功能如何实现呢? 

1、通过上面的<trim>标签和<if>标签实现

mapper:

  List<Userinfo> queryByConditin(Userinfo userinfo);

XML: 

   <select id="queryByConditin" resultType="com.example.mybatis.model.Userinfo">
        select * from userinfo
        <trim prefix="where" prefixOverrides="and">
        <if test="username != null">
            username = #{username}
        </if>
        <if test="age != null">
            and age = #{age}
        </if>
        <if test="gender != null">
            and gender = #{gender}
        </if>
        <if test="password != null">
            and password = #{password}
        </if>
        </trim>
    </select>

测试(查询姓名为:“lisi” 年龄为:16的用户): 

  @Test
    void queryByConditin() {
        Userinfo userinfo = new Userinfo();
        userinfo.setUsername("lisi");
        userinfo.setAge(16);
        System.out.println(mapper.queryByConditin(userinfo));

 这种查询方法固然能够成功,但是并不专业而且如果我们不添加任何查询条件时sql语句会多出来一个where导致Badsql异常,我们可以使用where标签来代替<trim>标签。

2、使用<where>标签实现

XML代码: 

    <select id="queryByConditin" resultType="com.example.mybatis.model.Userinfo">
        select * from userinfo
        <where>
        <if test="username != null">
            username = #{username}
        </if>
        <if test="age != null">
            and age = #{age}
        </if>
        <if test="gender != null">
            and gender = #{gender}
        </if>
        <if test="password != null">
            and password = #{password}
        </if>
        </where>
    </select>

测试: 

测试不加任何条件:

    @Test
    void queryByConditin() {
        Userinfo userinfo = new Userinfo();
        System.out.println(mapper.queryByConditin(userinfo));
    }

可以发现我们传入的sql语句并没有where。 

<where>标签详解:

<set>标签

与选择查询相同有时候我们也要更新一些用户的选项值,而保证其他值不变,比如:用户修改手机号、用户修改密码等。

同样的,这一功能也能通过<trim>标签和<if>标签实现:

mapper:

 Integer updateByConditin(Userinfo userinfo);

XML:

    <update id="updateByConditin">
    update userinfo
        <trim prefix="set" suffixOverrides=",">
        <if test="username != null">
            username = #{username},
        </if>
        <if test="age != null">
            age = #{age},
        </if>
        <if test="gender != null">
            gender = #{gender},
        </if>
        <if test="password != null">
            password = #{password},
        </if>
        </trim>
        where id = #{id}
    </update>

测试(修改id为6的用户名和密码): 

    @Test
    void updateByConditin() {
        Userinfo userinfo = new Userinfo();
        userinfo.setPassword("123456");
        userinfo.setUsername("wangwu");
        userinfo.setId(6);
        mapper.updateByConditin(userinfo);
    }

同样的,我们也可以使用<set>标签来代替这里的<trim>标签和<if>标签: 

    <update id="updateByConditin">
    update userinfo
        <set>
        <if test="username != null">
            username = #{username},
        </if>
        <if test="age != null">
            age = #{age},
        </if>
        <if test="gender != null">
            gender = #{gender},
        </if>
        <if test="password != null">
            password = #{password},
        </if>
        </set>
        where id = #{id}
    </update>

 测试(修改id为7的用户名和密码):

 @Test
    void updateByConditin() {
        Userinfo userinfo = new Userinfo();
        userinfo.setPassword("666666");
        userinfo.setUsername("wuwuwu");
        userinfo.setId(7);
        mapper.updateByConditin(userinfo);
    }


网站公告

今日签到

点亮在社区的每一天
去签到