Mybatis入门-06-动态SQL(if、choose、when、otherwise、set、sql、foreach)

一、前言

关于动态SQL,官方文档有很详细的总结。

一句话,拼接SQL很麻烦,甚至会因为缺少空格出错。利用动态SQL可以摆脱这种痛苦。

版本相关:

  • MySQL 8.0.19
  • MyBatis 3.5.5

参考视频:【狂神说Java】Mybatis最新完整教程IDEA版

二、搭建环境

2.1 创建表并插入数据

Create Table `blog`(
    `id` varchar(50) not null comment '博客id',
    `title` varchar(100) not null comment '博客标题',
    `author` varchar(30) not null comment '博客作者',
    `create_time` datetime not null comment '创建时间',
    `views` int(30) not null comment '浏览量'
)Engine=Innodb default charset = utf8;

insert into `blog`(`id`,`title`,`author`,`create_time`,`views`)
values
('97560394a5414655a571df20792c86cc','Mybatis入门-01-第一个程序','Hanxi','2020-09-10 00:31',25),
('9e328886846c4107a45ade2ed7259600','Mybatis入门-02-增删改查及配置(属性、别名、映射器)','Duzhuan','2020-09-11 14:08',5),
('57d837764f714efbbff4fcd335f2ddb1','日志工厂 ','Duzhuan','2020-09-11 22:49',12),
('f35f55cb414f4d3aadd75b8021e1b34e','Mybatis入门-04-多对一 ','Duzhuan','2020-09-19 20:49',5),
('c6f82f2bc929417c83736c28b307aa19','Mybatis入门-05-一对多','Duzhuan','2020-09-20 08:50',2);

2.2 依赖

对于不需要使用日志的,可以JUnit下面的关于日志的依赖删除。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.duzhuan</groupId>
    <artifactId>MyBaitsLearn</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <!--jdbc-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.19</version>
        </dependency>

        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
        </dependency>

        <!--JUnit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>


        <!--使用slf4j 作为日志门面-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <!--使用 log4j2 的适配器进行绑定-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.12.1</version>
            <scope>test</scope>
        </dependency>


        <!--log4j2 日志门面-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.12.1</version>
        </dependency>
        <!--log4j2 日志实现-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.12.1</version>
        </dependency>

    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/resource/</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/java/</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>

</project>

2.3 mybatis配置文件

路径

image-20200920105321251

文件

mybatis-config.xml

请按需要设置<setting>里的日志,不需要日志可以删除<settings>及其子标签;

请按需要设置别名,即 <package name="com.duzhuan.pojo"/>

请按照自己的Maven路径设置好<mappers>

同时关于mapUnderscoreToCamelCase,具体可看官方文档:

设置名 描述 有效值 默认值
mapUnderscoreToCamelCase 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 true | false False
<?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>

    <properties resource="db.properties"></properties>

    <settings>
        <setting name="logImpl" value="SLF4J"/>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    
    <typeAliases>
        <package name="com.duzhuan.pojo"/>
    </typeAliases>
    

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper class="com.duzhuan.dao.BlogMapper"/>
    </mappers>
</configuration>

db.properties

自行设置好JDBC配置,以下仅供参考:

driver = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF8&serverTimezone=UTC
username = root
password = qq123456

log4j2.xml

日志相关的配置,这里由于是SLF4J日志门面+LOG4J实现,因此在mybatis-config.xml中设置了SLF4J,具体看Mybatis入门-03-日志工厂

<?xml version="1.0" encoding="UTF-8"?>

<!--
    status="debug" 日志框架本身的级别
    configuration还有个属性是 monitorInterval = 5,自动加载配置文件的最小间隔时间,单位是秒
-->
<configuration status="debug">

    <!--
        集中配置属性进行管理,使用时通过:${}
    -->
    <properties>
        <property name="LOG_HOME">./logs</property>
    </properties>

    <!--日志处理器-->
    <!--先定义所有的appender -->
    <appenders>
        <!--这个输出控制台的配置 -->
        <Console name="Console" target="SYSTEM_OUT">
            <!--             控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
            <ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY"/>
            <!--             这个都知道是输出日志的格式 -->
            <PatternLayout pattern="%d{yyyy.MM.dd 'at' HH:mm:ss z} [%-5level] %class{36} %L %M - %msg%xEx%n"/>
        </Console>

        <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用 -->
        <!--append为TRUE表示消息增加到指定文件中,false表示消息覆盖指定的文件内容,默认值是true -->
        <File name="log" fileName="${LOG_HOME}/mybatis-log.log" append="false">
            <PatternLayout pattern="%d{yyyy.MM.dd 'at' HH:mm:ss z} [%-5level] %class{36} %L %M - %msg%xEx%n"/>
        </File>

        <!--
            添加过滤器ThresholdFilter,可以有选择的输出某个级别以上的类别
            onMatch="ACCEPT" onMismatch="DENY"意思是匹配就接受,否则直接拒绝
        -->
        <File name="ERROR" fileName="${LOG_HOME}/mybatis-error.log">
            <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="%d{yyyy.MM.dd 'at' HH:mm:ss z} [%-5level] %class{36} %L %M - %msg%xEx%n"/>
        </File>

        <!--
            使用随机读写流的日志文件输出appender,性能提高
        -->
        <RandomAccessFile name="accessFile" fileName="${LOG_HOME}/mybatis-access.log">
            <PatternLayout pattern="%d{yyyy.MM.dd 'at' HH:mm:ss z} [%-5level] %class{36} %L %M - %msg%xEx%n"/>
        </RandomAccessFile>

        <!--
            这个会打印出所有的信息,每次大小超过size,
            则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,
            作为存档
         -->
        <RollingFile name="RollingFile" fileName="${LOG_HOME}/mybatis-web.log"
                     filePattern="logs/$${date:yyyy-MM}/web-%d{MM-dd-yyyy}-%i.log.gz">
            <PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} [%-5level] %class{36} %L %M - %msg%xEx%n"/>
            <SizeBasedTriggeringPolicy size="2MB"/>
        </RollingFile>
    </appenders>


    <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效 -->
    <loggers>
        <!--使用rootLogger配置   日志级别level="trace" -->
        <root level="trace">
            <!--制定日志使用的处理器-->
            <appender-ref ref="log"/>
            <appender-ref ref="ERROR" />
            <appender-ref ref="Console"/>
            <appender-ref ref="accessFile"/>
            <appender-ref ref="RollingFile"/>
        </root>
    </loggers>
</configuration>

三、创建实体类

路径

image-20200920105248323

代码

Blog.java

package com.duzhuan.pojo;

import java.util.Date;

/**
 * @Autord: HuangDekai
 * @Date: 2020/9/20 10:30
 * @Version: 1.0
 * @since: jdk11
 */
public class Blog {
    private String  id;
    private String title;
    private String author;
    private Date createTime;
    private int views;

    public Blog() {
    }

    public Blog(String id, String title, String author, Date createTime, int views) {
        this.id = id;
        this.title = title;
        this.author = author;
        this.createTime = createTime;
        this.views = views;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public int getViews() {
        return views;
    }

    public void setViews(int views) {
        this.views = views;
    }

    @Override
    public String toString() {
        return "Blog{" +
                "id='" + id + '\'' +
                ", title='" + title + '\'' +
                ", author='" + author + '\'' +
                ", createTime=" + createTime +
                ", views=" + views +
                '}';
    }
}

四、创建常用工具类

路径

image-20200920120750067

代码

MyBatisUtils

package com.huangdekai.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

/**
 * @Autord: HuangDekai
 * @Date: 2020/9/20 10:36
 * @Version: 1.0
 * @since: jdk11
 */
public class MyBatisUtils {
    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            String config = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(config);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
}

IdUtils

package com.duzhuan.utils;

import java.util.UUID;

/**
 * @Autord: HuangDekai
 * @Date: 2020/9/20 12:00
 * @Version: 1.0
 * @since: jdk11
 */
public class IdUtils {
    public static String getId(){
        return UUID.randomUUID().toString().replace("-","");
    }
}

五、Mapper

路径

image-20200920121337527

六、IF标签

BlogMapper

package com.duzhuan.dao;

import com.duzhuan.pojo.Blog;

import java.util.List;

/**
 * @Autord: HuangDekai
 * @Date: 2020/9/20 10:58
 * @Version: 1.0
 * @since: jdk11
 */
public interface BlogMapper {
    List<Blog> getBlogList(Blog blog);
}

BlogMapper.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">
<mapper namespace="com.duzhuan.dao.BlogMapper">
    <select id="getBlogList" resultType="Blog">
        select * from mybatis.blog where 1=1
        <if test="title != null">
            and `title` like concat('%',#{title},'%')
        </if>
        <if test="author != null">
            and `author` = #{author}
        </if>
        <if test="views != -1">
            and `views` >= #{views}
        </if>
    </select>
</mapper>

  • 只是增加了使用if语句
  • test里放的是判断语句,多嘴一句,里面的如author != null中的authorBlog实体类的属性名,而不是数据库的字段名。
  • 实质上, select * from mybatiswhere 1=1where 1=1并不是一种规范的写法,甚至说不应该出现这种写法,这里仅仅用于说明<if>的使用,后面会使用where标签解决要使用where 1=1这种问题。

测试类

image-20200920170605091

package com.duzhuan.dao;

import com.duzhuan.pojo.Blog;
import com.duzhuan.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

/**
 * @Autord: HuangDekai
 * @Date: 2020/9/20 15:11
 * @Version: 1.0
 * @since: jdk11
 */
public class BlogMapperTest {
    @Test
    public void getBlogListTest(){
        SqlSession sqlSession = MyBatisUtils.getSqlSession();

        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        Blog blog1 = new Blog(null,"Mybatis",null,null,5);
        List<Blog> blogs = mapper.getBlogList(blog1);
        for (Blog blog : blogs) {
            System.out.println(blog);
        }

        sqlSession.close();
    }
}

结果:

image-20200920171111086

七、where标签

如果注意的话,可以看到上面的SQL语句有一个十分多余的东西,where 1=1

但是如果想要完成上面的功能,不用where 1=1这样类似的句子还不行,MyBatis的解决方法就是使用where标签,实质上和在SQL里使用where仅仅有一点点细微差别。

这方面官方文档-动态SQL中就写得十分翔实。

这里就对BlogMapper.xml做一点修改,尝试使用<where>标签去替代原来的句子:

<?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">
<mapper namespace="com.duzhuan.dao.BlogMapper">
    <select id="getBlogList" resultType="Blog">
        select * from mybatis.blog 
        <where>
            <if test="title != null">
                and `title` like concat('%',#{title},'%')
            </if>
            <if test="author != null">
                and `author` = #{author}
            </if>
            <if test="views != -1">
                and `views` >= #{views}
            </if>
        </where>
    </select>
</mapper>

结果:

image-20200920195923071

没错,真的只有一点点修改而已。

这是由于<where>的特性,下面是官方文档的说法:

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。

而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

八、choose-when-otherwise标签

这方面官方文档-动态SQL中写得十分翔实。

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

这里就对BlogMapperBlogMapper.xml做一点添加,尝试使得:

  • 有title时候返回搜索title的数据

  • 有author无title时候返回搜索author的数据

  • 仅有views时候返回大于等于views的数据,views默认输入0

BlogMapper中添加方法:

List<Blog> getBlogListByChoose(Blog blog);

BlogMapper.xml中在根标签<mapper></mapper>中添加:

<select id="getBlogListByChoose" resultType="Blog">
    select * from mybatis.blog
    <where>
        <choose>
            <when test="title != null">
                `title` like concat('%',#{title},'%')
            </when>
            <when test="title == null and author != null">
                `author` like concat('%',#{author},'%')
            </when>
            <otherwise>
                `views` >= #{views}
            </otherwise>
        </choose>
    </where>
</select>

测试样例:

@Test
public void getBlogListByChooseTest(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();

    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    Blog blog1 = new Blog(null, "日志", "hanxi", null, 10);
    Blog blog2 = new Blog(null, null, "hanxi", null, 10);
    Blog blog3 = new Blog(null, null, null, null, 10);

    List<Blog> blogListByChoose1 = mapper.getBlogListByChoose(blog1);
    List<Blog> blogListByChoose2 = mapper.getBlogListByChoose(blog2);
    List<Blog> blogListByChoose3 = mapper.getBlogListByChoose(blog3);

    System.out.println("blog1------->"+blogListByChoose1);
    System.out.println("blog2------->"+blogListByChoose2);
    System.out.println("blog3------->"+blogListByChoose3);

    sqlSession.close();
}

输出结果:

image-20200920203215956

九、set标签

上面的都是查询语句,那么动态的插入语句呢?

<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="email != null">email=#{email},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>

这是官方文档的一个例子。

可以看到,如果不使用<set>标签,一旦<if test="bio != null">bio=#{bio}</if>不成立而前面的SQL有成立的,那么就会多一个逗号,导致出错。

对于set,官方文档中有解释:

set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。

set使用方法与where类似,可以自行增加样例。

十、SQL标签

假如有很多个SQL,而且很多重复的,那么为了实现SQL的复用,就可以用到<SQL>标签。

BlogMapper.xml:

getBlogList<where>标签内的东西移出,放入<sql>标签内:

<?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">
<mapper namespace="com.duzhuan.dao.BlogMapper">

<!--=================add======================-->
    <sql id="title-author-views">
        <if test="title != null">
            and `title` like concat('%',#{title},'%')
        </if>
        <if test="author != null">
            and `author` = #{author}
        </if>
        <if test="views != -1">
            and `views` >= #{views}
        </if>
    </sql>
<!--==========================================-->
    
    <select id="getBlogList" resultType="Blog">
        select * from mybatis.blog
        <where>
<!--=====================update=========================-->
            <include refid="title-author-views"/>
<!--====================================================-->
        </where>
    </select>

    <select id="getBlogListByChoose" resultType="Blog">
        select * from mybatis.blog
        <where>
            <choose>
                <when test="title != null">
                    `title` like concat('%',#{title},'%')
                </when>
                <when test="title == null and author != null">
                    `author` like concat('%',#{author},'%')
                </when>
                <otherwise>
                    `views` >= #{views}
                </otherwise>
            </choose>
        </where>
    </select>
</mapper>

再用原来对这个方法的测试案例测试(BlogMapperTest的getBlogListTest方法):

image-20200921081315828

没问题。

  • 尽量基于单表
  • 不要带<where>标签

十一、foreach

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。

为了方便讲解,将数据库中的id改为1~5:

image-20200921083603525

比如实质要执行的语句:

select * from blog where id in (1,2,3,8,10)

BlogMapper.java中添加方法:

List<Blog> getBlogListByForeach(List numList);

BlogMapper.xml中添加:

<select id="getBlogListByForeach" resultType="Blog">
    select * from mybatis.blog
    where id in
    <foreach collection="list" item="item" index="index" open="(" close=")" separator=",">
        #{item}
    </foreach>
</select>

image-20200921091050071

结果:

image-20200921090534464

posted @ 2020-09-21 09:12  杜撰丶  阅读(233)  评论(0编辑  收藏  举报