8.1动态SQL中的元素
8.2<if>元素
举例,在映射文件中:
<select id="findCustomerByNameAndJobs" parameterType="com.itheima.po.Customer" resultType="com.itheima.po.Customer"> select * from t_customer where 1=1 <if test="username !=null and username !=''"> <!-- and username like concat('%',#{username},'%') --> and username like '%${username}%' </if> <if test="jobs !=null and jobs !=''"> and jobs= #{jobs} </if> </select>
test属性多用于判断,判断真假,大部分情况都是用作非空判断。有时候也需要判断字符串、数字和枚举等,如果传入的查询条件非空就进行动态SQL组装。
大白话:此时的作用,就是,如果username不空 并且 不为空值。(一个是空,没有地址,也没有值;另一个是有地址,也有值,但是值是空),u符合条件就拼接and username like '%${username}%'这条语句,jobs同理。
符合if条件拼起来的语句就是
select * from t_customer where 1=1 and username like '%${username}%' and jobs= #{jobs}
'%${username}%':%表示通配符,可以表示无限个字符,0也可以,这里表示只要有包含username的值就会被查出来,username的值会在对象customer被创建出来后设置。
在测试方法中:
/** * 根据客户姓名和职业组合条件查询客户信息列表 */ @Test public void findCustomerByNameAndJobsTest(){ // 通过工具类生成SqlSession对象 SqlSession session = MybatisUtils.getSession(); // 创建Customer对象,封装需要组合查询的条件 Customer customer = new Customer(); customer.setUsername("jack"); customer.setJobs("teacher"); // 执行SqlSession的查询方法,返回结果集 List<Customer> customers = session.selectList("com.itheima.mapper" + ".CustomerMapper.findCustomerByNameAndJobs",customer); // 输出查询结果信息 for (Customer customer2 : customers) { // 打印输出结果 System.out.println(customer2); } // 关闭SqlSession session.close(); }
大白话:创建对象customer,然后对它设值,表示要在表中查询像这个对象的数据项,这里表示要查找 username中有包含有“jack”(无论在哪个位置)并且jobs的值是“teacher”的数据项。把java语句映射成SQL语句去数据库里查询,把查询得到的结果放在Customer列表中,然后输出。在关闭执行器。
专业术语:使用Customer对象封装了用户名为jack且职业为teacher的查询条件,并通过SqlSession对象的selectList()方法执行多条件组合的查询操作。
如果把两条set语句注释掉,再次执行执行的SQL语句是:select * from t_customer where 1=1,所有数据项都会被查出来。这里表明:当未传递任何参数时,程序会将数据表中所有的数据查出,这就是<if>元素的使用。
如果在映射文件中去掉1=1这个真值,则SQL语句变成:select * from t_customer where and username like '%jack%' and jobs= ?
显然where后直接跟and会有语法错误。1=1的作用是避免where后面第一个词是and或者or之类的关键词,导致报错。
8.3 <choose>、<when>、<otherwise>元素
这些类似于java语句中的switch、case和default语句。
举例,在映射文件中:
<!--<choose>(<when>、<otherwise>)元素使用 --> <select id="findCustomerByNameOrJobs" parameterType="com.itheima.po.Customer" resultType="com.itheima.po.Customer"> select * from t_customer where 1=1 <choose> <when test="username !=null and username !=''"> and username like concat('%',#{username}, '%') </when> <when test="jobs !=null and jobs !=''"> and jobs= #{jobs} </when> <otherwise> and phone is not null </otherwise> </choose> </select>
大白话:在<choose>元素中,有好多好多个<when>元素,如果第一个<when>元素的test为真,只动态组装第一个<when>元素里的SQL片段,否则看第二个<when>元素,如果第二个为真,则只拼接第二个<when>元素里的SQL片段,以此类推。当前面所有的<when>元素中的test都不为真时,只组装<otherwise>元素内的SQL片段。相当于switch和case、default。
测试方法:
/** * 根据客户姓名或职业查询客户信息列表 */ @Test public void findCustomerByNameOrJobsTest(){ // 通过工具类生成SqlSession对象 SqlSession session = MybatisUtils.getSession(); // 创建Customer对象,封装需要组合查询的条件 Customer customer = new Customer(); customer.setUsername("jack"); customer.setJobs("teacher"); // 执行SqlSession的查询方法,返回结果集 List<Customer> customers = session.selectList("com.itheima.mapper" + ".CustomerMapper.findCustomerByNameOrJobs",customer); // 输出查询结果信息 for (Customer customer2 : customers) { // 打印输出结果 System.out.println(customer2); } // 关闭SqlSession session.close(); }
运行的SQL语句:select * from t_customer where 1=1 and username like concat('%',?, '%')
如果只注释掉customer.setUsername("jack")语句则运行的SQL语句:select * from t_customer where 1=1 and jobs= ?
如果只注释掉customer.setJobs("teacher")语句则运行的SQL语句:select * from t_customer where 1=1 and username like concat('%',?, '%')
如果都注释掉,则运行的SQL语句:select * from t_customer where 1=1 and phone is not null
concat起连接作用,实现模糊查询,这里效果等同'%${username}%'。
8.4 <where>、<trim>元素
又想防止语法错误又不想写1=1。
举例1,在映射文件中:
<!-- <where>元素 没有1=1的,用where元素代替 -->
<select id="findCustomerByNameAndJobs" parameterType="com.itheima.po.Customer" resultType="com.itheima.po.Customer"> select * from t_customer <where> <if test="username !=null and username !=''"> and username like concat('%',#{username},'%') </if> <if test="jobs !=null and jobs !=''"> and jobs= #{jobs} </if> </where> </select>
不仅不要1=1,连where都换成元素了。<where>元素会自动判断组合条件下拼接的SQL语句,只有<where>元素内的条件成立时,才会在拼接SQL中加入where关键字,否则将不会添加;即使where之后的内容有多余的and或者or都会自动删除。
举例2,在映射文件中:通过<trim>元素来定制需要的功能
<select id="findCustomerByNameAndJobs" parameterType="com.itheima.po.Customer" resultType="com.itheima.po.Customer"> select * from t_customer <trim prefix="where" prefixOverrides="and"> <if test="username !=null and username !=''"> and username like concat('%',#{username}, '%') </if> <if test="jobs !=null and jobs !=''"> and jobs= #{jobs} </if> </trim> </select>
专业话:<trim>元素的作用是除去一些特殊的字符串,他的prefix属性代表的是语句的前缀(这里使用where来连接后面的SQL片段),而prefixOverrides属性代表的是需要除去的那些特殊字符串(这里定义除去SQL中的and),上面的写法和使用<where>元素基本是等效的。
8.5<set>元素
set是设置的意思,不是集合的意思
在Hibernate中,想要更新某个对象,就需要发送所有的字段给持久化对象,这种想更新的每一条数据都要将其所有的属性都更新一遍的方法,其执行效率是非常差的。为此,在MyBatis中可以使用动态SQL中的<set>元素进行处理:使用<set>和<if>元素对username和jobs进行更新判断,并动态组装SQL。这样就只需要传入想要更新的字段即可。
举例,在映射文件中:
<update id="updateCustomer" parameterType="com.itheima.po.Customer"> update t_customer <set> <if test="username !=null and username !=''"> username=#{username}, </if> <if test="jobs !=null and jobs !=''"> jobs=#{jobs}, </if> <if test="phone !=null and phone !=''"> phone=#{phone}, </if> </set> where id=#{id}<!-- id=#{id}--> </update>
测试方法:
/** * 更新客户 */ @Test public void updateCustomerTest() { // 获取SqlSession SqlSession sqlSession = MybatisUtils.getSession(); // 创建Customer对象,并向对象中添加数据 Customer customer = new Customer(); customer.setId(3); customer.setPhone("13311111234"); customer.setUsername("bossli"); // 执行SqlSession的更新方法,返回的是SQL语句影响的行数 int rows = sqlSession.update("com.itheima.mapper" + ".CustomerMapper.updateCustomer", customer); // 通过返回结果判断更新操作是否执行成功 if(rows > 0){ System.out.println("您成功修改了"+rows+"条数据!"); }else{ System.out.println("执行修改操作失败!!!"); } // 提交事务 sqlSession.commit(); // 关闭SqlSession sqlSession.close(); }
运行结果:
如果映射文件中“where id=#”改成“where 1=1”则修改全部数据。
8.6 <foreach>元素
<!--<foreach>元素使用 --> <select id="findCustomerByIds" parameterType="List" resultType="com.itheima.po.Customer"> select * from t_customer where id in <foreach item="id" index="index" collection="list" open="(" separator="," close=")"> #{id} </foreach> </select>
item:配置的是循环中当前的元素。
index:配置的是当前元素在集合的位置下标。
collection:配置的是list传递过来的参数类型(首字母小写),它可以是一个array、list(或collection)、Map集合的键、POJO包装类中数组或集合类型的属性名等。
open和close:配置的是以什么符号将这些集合元素包装起来。
separator:配置的是各个元素的间隔符。
测试方法:
/** * 根据客户编号批量查询客户信息 */ @Test public void findCustomerByIdsTest(){ // 获取SqlSession SqlSession session = MybatisUtils.getSession(); // 创建List集合,封装查询id List<Integer> ids=new ArrayList<Integer>(); ids.add(1); ids.add(2); ids.add(3); //等于下面 // for(int i=1;i<=3;i++) // ids.add(i); // 执行SqlSession的查询方法,返回结果集 List<Customer> customers = session.selectList("com.itheima.mapper" + ".CustomerMapper.findCustomerByIds", ids); // 输出查询结果信息 for (Customer customer : customers) { System.out.println(customer); } // 关闭SqlSession session.close(); }
运行结果:
在使用<foreach>时最关键也是最容易出错的就是collection属性,该属性是必须指定的(按实际情况进行配置),而且在不同情况下,该属性的值是不一样的。主要有以下3种情况:
a)、如果传入的是单参数且参数类型是一个数组或者List的时候,collection属性值分别为array和list(或collection)。
b)、如果传入的参数是多个的时候,就需要把它们封装成一个Map了,当然单参数也可以封装成Map集合,这时候collection属性值就为Map的键。
c)、如果传入的参数是POJO包装类的时候,collection属性值就为该包装类中需要进行遍历的数组或集合的属性名。
8.7 <bind>元素
SQL语句:select * from t_customer where username like '%${value}%'
不妥之处:
a)、如果使用“${}”进行字符串拼接,则无法防止SQL注入问题;
b)、如果改用concat函数进行拼接,则只针对MySQL数据库有效;
c)、如果改用“||”进行字符串拼接,则只针对Oracle数据库有效。
总之:能用#就别用$
例如,在映射文件中:
<!--<bind>元素的使用:根据客户名模糊查询客户信息 --> <select id="findCustomerByName" parameterType="com.itheima.po.Customer" resultType="com.itheima.po.Customer"> <!--_parameter.getUsername()也可直接写成传入的字段属性名,即username --> <bind name="pattern_username" value="'%'+_parameter.getUsername()+'%'" /> select * from t_customer where username like #{pattern_username} </select>
专业话:<bind>元素定义了一个name为patter_username的变量,<bind>元素中value的属性值就是拼接的查询字符串,其中_parameter.getUsername()表示传递进来的参数(也可以直接写成对应的参数变量名,如username)。
其中_parameter这个东西是固定的。
测试方法:
/** * bind元素的使用:根据客户名模糊查询客户信息 */ @Test public void findCustomerByNameTest(){ // 通过工具类生成SqlSession对象 SqlSession session = MybatisUtils.getSession(); // 创建Customer对象,封装查询的条件 Customer customer =new Customer(); customer.setUsername("j"); // 执行sqlSession的查询方法,返回结果集 List<Customer> customers = session.selectList("com.itheima.mapper" + ".CustomerMapper.findCustomerByName", customer); // 输出查询结果信息 for (Customer customer2 : customers) { System.out.println(customer2); } // 关闭SqlSession session.close(); }
测试结果: