当前位置: 首页 > news >正文

MyBatis_3

上一篇文章,我们学习了使用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);

测试:

    @Testvoid getById() {System.out.println(mapper.getById(1));}@Testvoid 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);

测试:

   @Testvoid getByName() {System.out.println(mapper.getByName("xmy"));}@Testvoid 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);

 测试:

    @Testvoid getByName() {System.out.println(mapper.getByName("' OR 1 = '1"));}@Testvoid 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);

测试: 

    @Testvoid 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);

测试: 

 @Testvoid 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);

测试: 

  @Testvoid 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>

测试(有性别): 

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

测试(无性别): 

    @Testvoid 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>

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

 @Testvoid 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的用户): 

  @Testvoid 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>

测试: 

测试不加任何条件:

    @Testvoid 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的用户名和密码): 

    @Testvoid 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的用户名和密码):

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

http://www.xdnf.cn/news/1190053.html

相关文章:

  • 想入门网络编程?——网络通信的基本概念
  • Maven之依赖管理
  • ts-node 深入全面讲解
  • OCR工具集下载与保姆级安装教程!!
  • Spring Boot 实战:用 Apache Commons CSV 优雅解析 CSV 文件
  • MySQL 基本查询
  • Linux随记(二十一)
  • JVM 基础架构全解析:运行时数据区与核心组件
  • 商汤InternLM发布最先进的开源多模态推理模型——Intern-S1
  • 开源智能体框架(Agent Zero)
  • 从稀疏数据(CSV)创建非常大的 GeoTIFF(和 WMS)
  • Linux选择题
  • cacti的命令执行和回显
  • Python应用:三局两胜制石头剪刀布游戏
  • 人工智能发展历程
  • Linux应用开发基础知识——Framebuffer应用编程(六)
  • Linux用户
  • almalinux9.6-4070显卡-ollama-qwen2.5-7b
  • rt-thread 5.2.1 基于at-start-f437开发过程记录
  • Python 面向对象基础
  • 力扣刷题(第九十九天)
  • Rust嵌入式开发实战
  • 去除视频字幕 4 : 下一步,打算研究 Video Inpainting (视频修复):
  • Redis 缓存机制详解:原理、问题与最佳实践
  • Effective C++ 条款4:确定对象被使用前已先被初始化
  • 编程与数学 03-002 计算机网络 06_网络层职责
  • 设计模式十一:享元模式(Flyweight Pattern)
  • 路由选择工具——IP-Prefix
  • 如何查看电脑后门IP和流量?
  • 变频器实习DAY15