Mybatis动态sql

521 阅读4分钟

本文是Mybatis动态sql以及常用标签总结。

Mybatis是为数不多的跟着官网学习,就能学明白的,建议多看看官方文档。

传送门:Mybatis官网

    上一篇mybatis配置详解    ←||→    下一篇Mybatis结果集映射

动态sql

动态sql就是根据不同的条件拼接sql语句。

原生JDBC实现动态sql的缺陷

原生JDBC也可以实现动态sql,但是存在许多缺陷。

拼接时的空格

例如:

 //查询条件
 String deptN = "部门二";
 Integer isDelete = 0;
 //获取连接
 Connection conn = DriverManager.getConnection(url, user, pass);
 //编写sql
 String sql = "select * from department where 1 = 1";
 if (deptN != null)
     sql+="and deptName = ?";
 if (isDelete != null)
     sql+="and `delete` = ?";
 PreparedStatement prep = conn.prepareStatement(sql);
 System.out.println(sql);
 if (deptN != null)
     prep.setString(1, deptN);
 if (isDelete != null)
     prep.setInt(2, isDelete);

拼接过后的sql:

 select * from department where 1 = 1and deptName = ?and `delete` = ?

?and缺少空格,报SQLSyntaxErrorException sql语句错误。

sql最后的逗号

比如说批量插入时,需要去掉最后出现的逗号

 String sql = "insert into department(deptName) " +
         "values";
 for (Department dept : list) {
     sql += "(?),";
 }
 sql = sql.substring(0, sql.lastIndexOf(","));

mybatis动态sql

使用标签动态拼接sql且处理原生动态sql缺陷

mybatis相关配置:目录:测试环境搭建

相关标签
where标签

用于代替where关键字的,并且可以舍弃不必要的and,特别是配合if标签使用。

比如说根据id查询

mapper:接口

 public interface DepartmentMapper {
     //根据id查询
     Department queryById(@Param("id") Integer id);
 }

mapper.xml配置文件

 <select id="queryById" resultType="department">
     select * from department
     <where>
         and deptId = #{id}
     </where>
 </select>

测试:

 @Test
 public void testwhere(){
     SqlSession sqlSession = MybatisUtil.getSqlSession();
     DepartmentMapper mapper = sqlSession.getMapper(DepartmentMapper.class);
     Department department = mapper.queryById(1);
     System.out.println(department);
 }

结果:

image-20220312144237739.png

他会将第一个and省略
注意:但是他不会自动添加and。比如说有两个条件第一个条件and加不加都行,但是第二个and一定添加。

if标签

<if test = ""></if>
test条件满足时才拼接sql,同时第一个and也会被<where>处理

 <select id="queryByIdIf" resultType="department">
     select * from department
     <where>
         <if test="id ge 1">
             and deptId = #{id}
         </if>
     </where>
 </select>
choose、when、otherwise

官网:choose、when、otherwise

java中的switch case一样

jdk1.8switch是不是不支持判断引用类型?

 int i = 10;
 switch (i) {
     case 1:
         System.out.println("1");
         break;
     case 2:
         System.out.println("2");
         break;
     case 10:
         System.out.println("10");
         break;
     default:
         System.out.println("default");
 }
set标签

set 标签可以用于动态包含需要更新的列,忽略其它不更新的列,且会去除最后一个逗号。

 //更新
 int updateByEntity(Department dept);
 <update id="updateByEntity" parameterType="department">
     update department
     <set>
         <if test=" deptName!=null and deptName!='' ">
             deptName = #{deptName},
         </if>
         <if test=" delete gte 0 ">
             `delete` = #{delete},
         </if>
     </set>
     <where>
         deptId = #{deptId}
     </where>
 </update>

image-20220312150945311.png

会为我们补全set以及删除最后一个逗号 配合if是很好用的。

foreach标签

循环标签,多用于in判断和批量插入。

in判断

 //in   ids
 List<Department> queryIn(int[] ids);
 <select id="queryIn" resultType="department">
     select * from department
     <where>
         <foreach collection="array" item="item" index="index"
          open="deptId in (" separator="," close=")" >
             #{item}
         </foreach>
     </where>
 </select>

测试:

 @Test
 public void test4() {
     SqlSession sqlSession = MybatisUtil.getSqlSession();
     DepartmentMapper mapper = sqlSession.getMapper(DepartmentMapper.class);
     int[] ids = {4, 5, 6};
     List<Department> departments = mapper.queryIn(ids);
     for (Department dept : departments) {
         System.out.println(dept);
     }
 }

image-20220312155140590.png 没有问题可以查出来。

问题一:如果传入的参数为null,或者数组长度为0呢。

以前foreach标签 内是有 nullable属性的吧,现在没了

测试:出问题了 image-20220312155423516.png 解决:

利用if标签加判断,前提是 参数使用@Param修饰,@Param("ids") int[] ids,不然会有找不到参数异常。

 <select id="queryIn" resultType="department">
     select * from department
     <where>
         <if test=" ids==null or ids.length==0 ">
             1 = 2
         </if>
         <if test=" ids!=null and ids.length gt 0 ">
             <foreach collection="array" item="item" index="index"
                      open="deptId in (" separator="," close=")">
                 #{item}
             </foreach>
         </if>
     </where>
 </select>

问题二:foreach标签属性

你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

collection属性:collection、array、list
源码DefaultSqlsession image-20220312160253099.png

item and index

list set都基于数组实现。

如果迭代参数是arraylistset的话,index 是下标,item是元素。

如果迭代参数是 Map(或者 Map.Entry 对象的集合)时,index 是键(key),item 是值(value)。

通用:不用管迭代对象的类型,因为sqlsession会帮我帮我们处理,参数是啥就传啥。

mapper接口:

注意使用@Param修饰

 List<Department> queryIn(@Param("ids") int[] ids);

配置文件:

注意过滤参数为空的情况,且collection="ids"

 <select id="queryIn" resultType="department">
     select * from department
     <where>
         <if test=" ids==null or ids.length==0 ">
             1 = 2
         </if>
         <if test=" ids!=null and ids.length gt 0 ">
             <foreach collection="ids" item="item" index="index"
                      open="deptId in (" separator="," close=")">
                 #{item}
             </foreach>
         </if>
     </where>
 </select>

测试:

 int[] ids = {4, 5, 6};
 List<Department> departments = mapper.queryIn(ids);

结果: image-20220312162042035.png

使用map:

//in   map
List<Department> queryInMap(@Param("map") Map map);
<select id="queryInMap" resultType="department">
    select * from department
    <where>
        <if test=" map == null or map.size==0 ">
            1 = 2
        </if>
        <if test=" map!=null and map.size gt 0 ">
            <foreach collection="map" item="value" index="key"
                     open="deptId in (" separator="," close=")">
                <!--    #{item}           -->
                #{value}
            </foreach>
        </if>
    </where>
</select>
@Test
public void test5() {

    SqlSession sqlSession = MybatisUtil.getSqlSession();

    DepartmentMapper mapper = sqlSession.getMapper(DepartmentMapper.class);

    Map<String, Integer> map = new HashMap<String, Integer>();

    map.put("id1",1);
    map.put("id2",2);
    map.put("id3",3);

    List<Department> departments = mapper.queryInMap(map);

    for (Department dept : departments) {
        System.out.println(dept);
    }
}

image-20220312162702676.png

trim标签

自定义标签。

使用trim实现where

 <select id="trimToWhere" resultType="department">
     select * from department
     <trim prefix="where" prefixOverrides="and |or">
         and deptId = #{id}
     </trim>
 </select>
 prefix           前缀
 prefixOverrides  覆盖最前面的  and or  and后面的空格必要
 suffix           后缀
 suffixOverrides  覆盖最后面

实际应用:插入语句,对于插入数据的判断

 <trim prefix="(" suffix=")" suffixOverrides=",">
     <if test="blogId != null">blog_id,</if>
     <if test="blogTitle != null">blog_title,</if>
     <if test="blogSubUrl != null">blog_sub_url,</if>
     <if test="blogCoverImage != null">blog_cover_image,</if>
 </trim>

sql片段

<sql>定义片段,<include>引入片段

多用于定义必要常用字段,或者排除占用内存较大字段,提升sql性能。

 <sql id="department_base_colum">
     deptId,deptName,`delete`,create_time,modify_time
 </sql>
 <select id="baseColum" resultType="department">
     select
     <include refid="department_base_colum"/>
     from department
     <trim prefix="where" prefixOverrides="and |or">
         and deptId = #{id}
     </trim>
 </select>