目录:
-
- [动态SQL中的 "元素" :](#动态SQL中的 “元素” :)
作者简介 :一只大皮卡丘,计算机专业学生,正在努力学习、努力敲代码中! 让我们一起继续努力学习!
该文章参考学习教材 为:
《Java EE企业级应用开发教程 (Spring + Spring MVC +MyBatis)》 黑马程序员 / 编著文章以课本知识点 + 代码为主线,结合自己看书学习过程中的理解和感悟 ,最终成就了该文章
文章用于本人学习使用 , 同时希望能帮助大家。
欢迎大家点赞👍 收藏⭐ 关注💖哦!!!
(侵权可联系我,进行删除,如果雷同,纯属巧合)
- 在使用 JDBC 或其他类似的框架 进行数据库开发 时,通常都要根据需求去手动拼装SQL ,这是一个非常麻烦且痛苦的工作,而 MyBatis 提供的对SQL语句动态组装的功能,能很好地解决这一麻烦工作。
动态SQL中的 "元素" :
动态SQL 是MyBatis 的强大特性之一 ,MyBatis3 采用了功能强大的基于 OGNL的表达式 来 完成动态SQL,
它消除 了之前版本中需要了解 的大多数元素 ,使用不到原来一半 的元素 就能完成所需工作。MyBatis动态SQL 中的 主要元素 (用在 "映射文件 " 中),如下表所示 :
元素 说明 <if> 判断语句 ,用于单条件分支判断。 <choose> ( <when>、<otherwise> ) 相当于Java中的switch...case...default语句 ,用于多条件分支判断。 <where>、<trim>、 <set> 辅助元素 ,用于处理一些SQL拼装 、特殊字符问题。 <foreach> 循环语句 ,常用于 in语句 等列举条件中 <bind> 从OGNL表达式 中创建一个变量 ,并将其绑定到上下文 ,常用于模糊查询的sql中。
<if>元素
在MyBatis中,<if>元素 是最常用的判断语句,它类似于Java 中的 if语句 ,主要用于实现某些简单 的条件选择 。在实际开发中, 我们可能会通过多个条件 来精确地查询某个数据 。<if>元素 要结合其的 test属性一起使用。使用 <if>元素时 ,只要test属性中 的表达式为true ,就会执行元素中 的条件语句。
例如,要查找++某个客户的信息++ , 可以通过++姓名++ 和++职业++ 来查找客户,也可以++不填写++ ++职业++ 直接通过++姓名++ 来++查找客户++ ,还可以++都不填写++ 而查询出所有客户,此时++姓名和职业就是非必须条件++ ( 可能通过这两个条件来查询,也可能取其中之一来进行查询,如果此时还要程序员通过代码来判断就会显得很麻烦,可通过动态SQL的 if元素 来根据实际情况来进行 "查询")。类似于这种情况,在 MyBatis 中就可以通过 <if>元素来实现。
<if>元素 进行 "动态SQL" 操作例子 如 :
Customer.java
java//使用注解来为该POJO类设置在 mybatis-config.xml配置文件中使用的 "别名" @Alias(value = "customer") // Alias : 设置别名 public class Customer { // "顾客"类 --"持久化"类 private Integer id; //主键 private String username; //客户名称 private String jobs; //职业 private String phone; //电话 public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getJobs() { return jobs; } public void setJobs(String jobs) { this.jobs = jobs; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } @Override public String toString() { return "Customer{" + "id=" + id + ", username='" + username + '\'' + ", jobs='" + jobs + '\'' + ", phone='" + phone + '\'' + '}'; } }
mybatis-config.xml :(配置文件)
xml<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- Mybatis的配置文件: mybatis-config.xml : 为全局配置文件,配置了"运行环境的信息", 主要内容是 : 数据库的连接 (其属于Mybatis操作步骤的第一步 : 读取Mybatis-config.xml配置文件的"先行要准备好的内容") --> <!-- 1.配置环境,默认环境id为mysql --> <environments default="mysql"> <environment id="mysql"> <transactionManager type="JDBC"/> <!-- 用于指定MyBatis获取数据库连接的方式。"POOLED"代表的是连接池。 --> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <!-- 2.配置Mapper文件的位置 : mybatis.config.xml配置文件要读取映射文件: 即读取mapper.xml文件 --> <!-- 因为按照"Mybatis框架的执行流程图 : 加载了mybatis-config.xml配置文件之后,是要加载"映射文件"的,所以 --> <!-- 读取"映射文件" --> <mappers> <mapper resource="CustomerMapper.xml"/> </mappers> </configuration>
CustomerMapper.xml :(映射文件)
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"> <!-- namespace的命名空间 --> <mapper namespace="CustomerMapper"> <!-- 使用resultMap解决 "属性名" 和 "字段名"不一致的,使得数据能成功映射到POJO类中 --> <resultMap id="resultMap_Customer" type="Customer"> <id property="id" column="t_id"/> <result property="username" column="t_username"/> <result property="jobs" column="t_jobs"/> <result property="phone" column="t_phone"/> </resultMap> <!-- 动态SQL,解决实际开发中"查询"条件不确定,让程序自行判断是否要将其加入到"查询SQL语句"中的情况 --> <!-- if元素使用 --> <!-- test="username != null and username != '' 对username属性进行 "非空判断" --> <select id="findCustomerByNameAndJob" parameterType="com.myh.po.Customer" resultMap="resultMap_Customer"> select * from t_customer where 1=1 <if test="username != null and username != ''"> and t_username like concat('%',#{username},'%') </if> <if test="jobs != null and jobs !=''"> and t_jobs = #{jobs} </if> </select> </mapper>
动态SQLTest.java : (测试类 )
java![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=Typora%2F%E7%9F%A5%E8%AF%86%E7%82%B9%E5%9B%BE%E7%89%87%2FMybatis%2F%E5%8A%A8%E6%80%81SQL%2F%E6%8E%A7%E5%88%B6%E5%8F%B0%E8%BF%90%E8%A1%8C%E7%BB%93%E6%9E%9C.jpg&pos_id=img-uEcUK4Sw-1709389766682)public class 动态SQLTest { /** * 根据客户姓名和职业组合条件查询客户信息列表 */ @Test //单元测试 public void findCustomerByNameAndJobsTest() throws IOException { //1.读取mybatis-config.xml配置文件 (通过"输入流"来读取) String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); //2.根据配置文件(通过SqlSessionFactoryBuilder对象 )创建"会话工厂"对象 : SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //3.获得SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); //创建Customer对象,组合封装组合查询的条件 Customer customer = new Customer(); customer.setUsername("小明"); //执行SqlSession的selectList()方法 List<Customer> customers = sqlSession.selectList("CustomerMapper.findCustomerByNameAndJob", customer); //输出查询结果 for (Customer customer1 : customers) { System.out.println(customer1); } //管边SqlSession sqlSession.close(); } }
控制台运行结果 :
从上图可以看出 ,传递的只有一个参数 username='小明',另一个属性没传递参数 ,此时"映射文件 "中进行了动态SQL 来动态的根据参数 来查询数据库中 的数据。
<choose>、<when>、<otherwise>元素
在使用 <if>元素时,只要test属性中 的表达式为true ,就会执行元素中 的条件语句 ,但是在实际应用 中,有时只需要从多个选项中选择一个去执行。
例如下面的场景 :
当客户名称不为空 ,则只根据客户名称 进行客户筛选 ;
当客户名称为空 ,而客户职业不为空 ,则只根据客户职业 进行客户筛选 。
当客户名称和客户职业都为空 ,则要求查询出所有电话不为空 的客户信息 。"
此种情况下,使用 <if>元素 进行处理是非常不合适 的。如果使用的是Java语言 ,这种情况显然更适合使用 switch...case...default语句来处理。针对以上这种情况 / 这种开发需求,MyBatis 中可以使用 <choose>、<when>、 <otherwise> 元素进行处理。
例子如 :
CustomerMapper.xml :
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"> <!-- namespace的命名空间 --> <mapper namespace="CustomerMapper"> <!-- 使用resultMap解决 "属性名" 和 "字段名"不一致的,使得数据能成功映射到POJO类中 --> <resultMap id="resultMap_Customer" type="com.myh.po.Customer"> <id property="id" column="t_id"/> <result property="username" column="t_username"/> <result property="jobs" column="t_jobs"/> <result property="phone" column="t_phone"/> </resultMap> <!-- <choose> <when> <otherwise> 元素的使用 (动态SQL) --> <!-- <choose> <when> <otherwise> 分别代表 Java中的 switch case default (功能也是相同的,前面的获取了,后面的就不再获取了) --> <select id="findCustomerByNameAndJob2" parameterType="com.myh.po.Customer" resultMap="resultMap_Customer"> select * from t_customer where 1=1 <choose> <when test="username != null and username != ''"> and t_username like concat('%',#{username},'%') </when> <when test="jobs != null and jobs != ''"> and t_jobs = #{jobs} </when> <otherwise> and t_phone is not null </otherwise> </choose> </select> </mapper>
在上述代码中,使用了 <choose>元素 进行SQL拼接 ,当第一个 <when>元素 中的条件为真 ,则只动态组装第一个<when>元素 内 的SQL片段,否则 就继续向下 判断第二个<when>元素 中的条件是否为真,以此类推。当前面所有 <when>元素中 的条件都不为真 时,则只组装<otherwise>元素内的SQL片段。
动态SQLTest.java
javapublic class 动态SQLTest { /** * 根据客户姓名、职业和电话号码进行类型于 switch、case、default式开发 */ @Test //单元测试 public void findCustomerByNameAndJobsTest2() throws IOException { //1.读取mybatis-config.xml配置文件 (通过"输入流"来读取) String resource = "com/myh/动态SQL/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); //2.根据配置文件(通过SqlSessionFactoryBuilder对象 )创建"会话工厂"对象 : SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //3.获得SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); //创建Customer对象,组合封装组合查询的条件 Customer customer = new Customer(); customer.setJobs("学生"); //执行SqlSession的selectList()方法 List<Customer> customers = sqlSession.selectList("CustomerMapper.findCustomerByNameAndJob2", customer); //输出查询结果 for (Customer customer1 : customers) { System.out.println(customer1); } //管边SqlSession sqlSession.close(); } }
控制台输出结果1 :
上述的输出结果 是 :只有一个jobs 的参数 : 学生。所以按照switch 、case 、default ,被"映射文件 "中的第二个<when>元素拦截,所以有以上结果。
控制台输出结果2 :
将 customer.setJobs("学生");语句注释掉,则会按照第三个条件 :查询出所有电话不为空的客户信息 。相当于用default / <otherwise> 元素中的sql语句 来查询数据库。 输出内容如下所示 :
<where>、<trim>元素
在前两个元素的案例 中,映射文件 中编写的SQL后面都加入了"where 1=1"的条件,那么到底为什么要这么写呢?如果将where 后"1=1" 的条件去掉,那么MyBatis所拼接出来的SQL将会如下所示 :
xmlselect * from t_customer where 1=1 and t_username like concat('%',?,'%')
上面SQL中,where后直接跟 的是and ,这在运行时肯定会报SQL语法错误 ( 开发中,如果没有where 1=1 ,直接用 if 和 choose元素 的话是会报错的 ),而加入了条件"1=1"后,既保证了where后面的条件成立,又避免了where后面第一个词是 and或者or之类的关键词。那么在MyBatis 中,有没有什么办法不用加入"1=1"这样的条件 ,也能使拼接后的SQL成立 呢?
-----MyBatis 提供了 <where>元素 来处理这样的问题。 ( 将 where 1 = 1删除 , 用<where>元素进行替换)
xml<!-- 用<where>元素来替代 where 1=1 --> <select id="findCustomerByNameAndJob3" parameterType="com.myh.po.Customer" resultMap="resultMap_Customer"> select * from t_customer <where> <if test="username != null and username != ''"> and t_username like concat('%',#{username},'%') </if> <if test="jobs != null and jobs !=''"> and t_jobs = #{jobs} </if> </where> </select>
上述配置代码 中,使用 <where>元素 对"where 1=1 " 条件 进行了替换,<where>元素 会自动判断 组合条件下拼装的SQL语句, 只有 <where>元素内的条件成立 时,才会在拼接SQL中 加入where关键字 ,否则将不会添加 ; 即使where之后 的内容 有多余的"AND " 或 "OR ",<where>元素也会自动将它们去除。
除了 使用 <where>元素外 ,还可以通过 <trim>元素 来定制需要的功能 ,上述代码 还可以修改为如下形式
xml<!-- <trim>元素 --> <!-- prefix : 表示语句的前缀, prefix="where" : 用where来进行sql语句拼接 --> <!-- prefixOverrides : 表示"要去除的特殊字符", prefixOverrides="and" : 去除sql语句中的and --> <select id="findCustomerByNameAndJob4" parameterType="com.myh.po.Customer" resultMap="resultMap_Customer"> select * from t_customer <trim prefix="where" prefixOverrides="and"> <if test="username != null and username != ''"> and t_username like concat('%',#{username},'%') </if> <if test="jobs != null and jobs !=''"> and t_jobs = #{jobs} </if> </trim> </select>
上述配置代码 中,同样使用 <trm>元素 对 "where1=1" 条件进行了替换****,<trim>元素的作用 是去除一些特殊的字符串 , 它的 prefix属性 代表的是语句的前缀 ( 这里使用where 来连接后面 的SQL片段 ) ,而 prefixoOverrides属性 代表的是需要去除 的那些特殊字符串 ( 这里定义了要去除SQL 中的and ),上面的写法和使用 <where>元素 基本是等效的。
<set>元素
在Hibernate 中,如果想要更新某一个对象 , 就需要发送所有的字段给持久化对象 ,然而实际应用中,大多数情况下都是更新 的某一个 或几个字段 。 如果更新的每一条数据 都要将其所有的属性 都更新一遍 ,那么其执行效率 是非常差的
( 而且实际开发中要修的内容是不定的,如果要预估所有的情况而提前设置好合适的set语句来修改数据库,显然是不合理的,这时可用动态SQL来解决,传来什么数据则设置修改什么数据 )。为了解决上述情况中 的问题 ,MyBatis中提供了 <set>元素 来完成这一工作。<set>元素主要用于更新操作 ,其主要作用 是在动态包含的SQL语句前输出一个SET关键字 ,并将SQL语句 中最后一个多余的逗号去除。 (根据传入的"参数"的情况来动态的修改 数据库中的"数据",通过<set>元素中的<if>元素来动态判断)
xml<!-- <set>元素 : 动态修改操作 --> <!-- <set>元素会动态判断去修改数据库中的数据,同时会将多余的"逗号"去除掉 --> <update id="updateCustomer" parameterType="com.myh.po.Customer"> update t_customer <set> <if test="username != null and username != ''"> set t_username = #{username}, </if> <if test="jobs != null and jobs != ''"> set t_jobs=#{jobs}, </if> <if test="phone != null and phone != ''"> set t_phone=#{phone}, </if> </set> where id = #{id} </update>
注意 :
在映射文件 中使用 <set>和 <if>元素组合进行update语句动态SQL组装时 ,如果 <set> 元素 内包含的内容都为空 ,则会出现SQL语法错误。所以在使用 <set>元素 进行字段信息更新 时,要确保传入的更新字段不能都为空。
<foreach>元素
在实际开发中,有时可能会遇到这样的情况 : 假设在一个客户表 中有 1000条数据 ,现在需要将id值小于 100 的客户信息全部查询出来,这要怎么做呢? 有人也许会说,"我可以一条一条查出来 ",那如果查询200 、300 甚至更多也一条条查吗? 这显然是不可取 的。
有的人会想到,可以在Java方法 中使用循环 ,将查询方法放在循环语句中,然后通过条件循环的方式查询出所需的数据。这种查询方式虽然可行,但每执行一次循环语句, 都需要向数据库中发送一条查询SQL,其查询效率是非常低的 。此时我们可以通过SQL语句 来执行这种查询。
MyBatis 中已经提供了一种用于数组 和 集合循环遍历 的方式 ,那就是使用 <foreach>元素 ,我们完全可以通过 <foreach>元素 来解决上述类似的问题。
<foreach>元素 通常在构建IN条件语句时使用,其使用方式如下 :
xml<!-- <foreach>元素使用 --> <!-- <foreach>元素通常在构建IN条件语句中使用 --> <select id="findCustomerByIds" parameterType="List" resultMap="resultMap_Customer"> select * from t_customer where id in <foreach item="id" index="index" collection="list" separator="," open="(" close=")"> #{id} </foreach> </select>
假设上述 传入的id集合 中内容为 : 1,3,5 。那么上述代码 中 最后拼接的sql 语句 为 : select * from t_customer where id in (1,3,5) ( foreache元素 中的各个属性 的作用 将在下面中讲述。)
( 通过以上代码可实现 <foreach> 元素 对传入的客户编号集合 进行了动态SQL组装 ,最终成功批量 查询出了对应的客户信息。)
在上述"映射文件 "代码中,使用了 <foreach>元素 对传入的集合 进行遍历 并进行了动态SQL组装。关于 <foreach>元素 中使用的几种属性 的描述具体如下:
属性 描述 item 配置 循环中当前的 元素。 如 : 传入一个1,3,5 的id集合 ,item 分别代表其中的1,3,5。 index 配置 当前元素 在集合 的位置下标。 collection 配置 传递过来的 "参数类型" ( 首字母小写 )。它可以是一个array 、list ( 或 collection )、Map 集合 的键 、POJO包装类 中数组 或 集合类型 的属性名等。 open "遍历所有元素" 之前 要 "插入 / 配置" 的 "前缀"。 close "遍历所有元素" 之后 要 "插入 / 配置" 的 "后缀"。 open 和 close 配置的是 以什么符号 将这些集合元素包装起来。 separator 配置的是 各个元素 的间隔符。 注意一:
可迭代对象** (如列表 、集合 等) 和任何的字典 或者数组对象 传递给 <foreach>作为集合参数 。当使用 可迭代对象或者数组 时,index 是当前迭代的次数,item 的值是本次迭代获取的元素。
当使用字典(或者Map.Entry对象的集合)时,index 是键,item 是值。
注意二:
在使用 <foreach> 时最关键也是最容易出错 的就是collection 属性 ,该属性是必须指定的,而且在不同情况下 ,该属性的值是不一样 的。主要有以下3种情况 。
(1) 如果传入的是 单参数且参数类型是一个数组 或者List 的时候,collection 属性值 分别为
array 和 list ( 或collection )。
(2) 如果传入的参数是多个 的时候,就需要把它们封装成一个Map 了,当然单参数 也可以
封装成Map集合,这时候==collection属性值 就为Map 的键。
(3) 如果传入的参数是POJO包装类 的时候,collection属性值就为该包装类中需要进行遍历的数组或集合 的 属性名 。
设置collction 属性值 的时候,必须按照实际情况配置 ,否则 程序就会出现异常。
Test.java (测试类):
java/** * 根据客户端"编号"查询客户信息 */ @Test //单元测试 public void findCustomerById() throws IOException { //1.读取mybatis-config.xml配置文件 (通过"输入流"来读取) String resource = "com/myh/动态SQL/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); //2.根据配置文件(通过SqlSessionFactoryBuilder对象 )创建"会话工厂"对象 : SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //3.获得SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); //创建List集合,封装查询id List<Integer> ids = new ArrayList<Integer>(); ids.add(1); ids.add(2); //执行SqlSession的查询方法 List<Customer> customers = sqlSession.selectList("CustomerMapper3.findCustomerByIds", ids);//传入参数是List类型,所以collection="list" //打印输出结果 for (Customer customer = customers ) { System.out.println(customer); } //关闭SqlSession }
上面传的 参数 是类型 是List类型 ,所以 "映射文件 "中的collection的值 为: list。同时集合中的id数据 为 : 1,2 。 "映射文件 " 会根据传入的1,2来进行数据库查询。
<bind>元素
在进行 模糊查询 编写SQL语句的时候,如果使用 "${}" 进行 字符串拼接,则无法防上sql注入问题 ;
如果使用concat函数 进行拼接,则只针对MySQL数据库有效 ;如果使用的是Oracle数据库 ,则要使用连接符号" ||"。 这样,映射文件中的SQL 就要根据不同的情况提供不同形式 的实现 ,这显然是比较麻烦 的,且不利于项目的移植 。为此,MyBatis 提供了 <bind>元素 来解决这一问题,我们完全不必使用数据库语言 ,只要使用MyBatis的语言 即可与所需参数连接。MyBatis的 <bind>元素 可以通过OGNL表达式 来创建一个上下文变量 , 其使用方式如下 :
"映射文件" 中代码 :
xml<!-- <bind>元素的使用: 根据客户名模糊查询"客户信息" --> <select id="findCustomerByName" parameterType="List" resultMap="resultMap_Customer"> -- _parameter.getUsername()也可直接写入传入的字段属性名,即username <bind name="pattern_username" value="'%'+_parameter.getUsername()+'%'"/> select * from t_customer where username like #{pattern_username} </select>
上述配置代码中,使用 <bind>元素 定义了一个name 为 pattern_username 的变量,<bind>
元素中value 的属性值就是拼接的查询字符串 ,其中 _parameter.getUsername( )表示传递进来的
参数 (也可以直接写成对应的参数变量名 ,如username )。在SQL语句中,直接引用 <bind>元
素 的name属性值 即可进行动态SQL组装。
单元测试 :
java/** * <bind>元素的使用 : 根据客户名模糊查询客户信息 (使用<bind>元素进行动态SQL组装) */ @Test //单元测试 public void findCustomerByNameTest() throws IOException { //1.读取mybatis-config.xml配置文件 (通过"输入流"来读取) String resource = "com/myh/动态SQL/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); //2.根据配置文件(通过SqlSessionFactoryBuilder对象 )创建"会话工厂"对象 : SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //3.获得SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); Customer customer = new Customer(); customer.setUsername("明"); //执行SqlSession的查询方法,返回结果集 List<Customer> customers = sqlSession.selectList("CustomerMapper3.findCustomerByName", customer); //输出结果 for (Customer customer1 : customers) { System.out.println(customer1); } }