mybatis-动态SQL


  • 动态SQL【code that is executed dynamically】



    1.为什么会有Mybatis动态SQL?

    实际业务中一定会经常遇到按照很多查询条件进行查询的情况,比如京东根据不同的条件筛选商品。其中经常出现很多条件不取值的情况(只有单个或者几个条件进行查询),在后台应该如何完成最终的SQL语句呢?

    如果采用JDBC进行处理,需要根据条件是否取值来进行SQL语句的拼接,一般情况下是使用StringBuilder类及其append方法实现,还是有些繁琐的,或者利用反射,比如:

    preparedStatement = connection.prepareStatement(sql);
    //可能设置了查询的条件,所以需要参数设置
    for (int i = 0; i < args.length; i++) {
    preparedStatement.setObject(i+1,args[i]);
    }
    resultSet = preparedStatement.executeQuery();
    //根据字节码文件获取属性:
    Field[] fields = clazz.getDeclaredFields();
    //设置私有属性为可访问(注意:这个baseQuery方法不是很安全,是一种临时的解决办法)
    for (Field field : fields) {
    field.setAccessible(true);
    }
    //根据反射创建对象
    while (resultSet.next()){
    //创建实例
    Object o = clazz.newInstance();
    for (Field field : fields) {
    //根据field获取属性的名称
    String name = field.getName();
    //根据field获取的属性名称,获取resultSet中的数据
    Object data = resultSet.getObject(name);
    //设置实例的属性值
    field.set(o,data);
    }

    如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。

    MyBatis在简化操作方法提出了动态SQL功能,将使用Java代码拼接SQL语句,改变为在XML映射文件中截止标签拼接SQL语句。相比而言,大大减少了代码量,更灵活、高度可配置、利于后期维护。

    MyBatis中动态SQL是编写在mapper.xml中的,其语法和JSTL类似,但是却是基于强大的OGNL表达式实现的。

    MyBatis也可以在注解中配置SQL,但是由于注解功能受限,尤其是对于复杂的SQL语句,可读性很差,所以较少使用。

    所以说了半天,动态SQL就是:

    让我们在XML映射文件内,以标签的形式编写动态的SQL,完成逻辑判断和动态拼接SQL 的功能。

    Mybatis 提供了 9 种动态 SQL 标签:<if /><choose /><when /><otherwise /><trim /><where /><set /><foreach /><bind />

    2.常用的动态SQL的标签

    准备环境

    新建一个Maven项目(或者模块module)Mybatis_pro04

    mybatis-动态SQL

    pom.xml中引入以下配置和依赖:

    <build>
    <plugins>
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
    <source>8</source>
    <target>8</target>
    </configuration>
    </plugin>
    </plugins>
    </build>
    <packaging>jar</packaging>
    <dependencies>
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version>
    </dependency>

    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.11</version>
    </dependency>

    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
    </dependency>

    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
    <scope>provided</scope>
    </dependency>

    <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
    </dependency>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
    <scope>compile</scope>
    </dependency>
    </dependencies>

    引入配置和依赖之后,可以手动重新加载maven项目:

    mybatis-动态SQL
    image-20221015114852004

    注意:关于lombok的使用,需要安装Lombok插件(plugins)和引⼊Lombok依赖(pom.xml中)

    (有些版本的IDEA可能比较抽风,需要手动加载,蔽日我的IDEA)

    怎么验证加载有没有成功?

    mybatis-动态SQL

    然后搭建项目结构:

    【1】创建相关包和配置文件。(特别注意:resources目录下,包的创建必须一层一层,这个是IDEA的bug)

    mybatis-动态SQL

    【2】书写配置文件:

    jdbc.properties(对于mysql8.0版本,下面的jdbc_url最好这么写,否则会有无法避免的错误)

    jdbc_driver=com.mysql.cj.jdbc.Driver
    jdbc_url=jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    jdbc_username=你的数据库的用户名
    jdbc_password=你的数据库的密码

    log4j.properties 版本1.2.17

    #定义全局日志级别调试阶段推荐debug
    log4j.rootLogger=debug,stdout

    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.Target=System.err
    log4j.appender.stdout.layout=org.apache.log4j.SimpleLayout

    log4j.appender.logfile=org.apache.log4j.FileAppender
    log4j.appender.logfile.File=d:/xxx.log
    log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
    log4j.appender.logfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %l %F %p %m%n

    如果版本是log4j2,则需要在resources文件下创建log4j2.xml,文件内容如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="DEBUG">
    <Appenders>
    <Console name="Console" target="SYSTEM_ERR">
    <PatternLayout pattern="%d{YYYY-MM-dd HH:mm:ss} [%t] %-5p %c{1}:%L - %msg%n" />
    </Console>

    <RollingFile name="RollingFile" filename="log/test.log"
    filepattern="${logPath}/%d{YYYYMMddHHmmss}-fargo.log">

    <PatternLayout pattern="%d{YYYY-MM-dd HH:mm:ss} [%t] %-5p %c{1}:%L - %msg%n" />
    <Policies>
    <SizeBasedTriggeringPolicy size="10 MB" />
    </Policies>
    <DefaultRolloverStrategy max="20" />
    </RollingFile>

    </Appenders>
    <Loggers>
    <Root level="INFO">
    <AppenderRef ref="Console" />
    </Root>
    </Loggers>
    </Configuration>

    sqlMapConfig.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "https://mybatis.org/dtd/mybatis-3-config.dtd">

    <configuration>
    <properties resource="jdbc.properties" />
    <settings>
    <setting name="logImpl" value="LOG4J"/>
    </settings>
    <typeAliases>
    <!--默认每个实体类的别名是首字母小写的类名-->
    <package name="com.bones.pojo"/>
    </typeAliases>
    <environments default="development">
    <environment id="development">
    <transactionManager type="JDBC"/>
    <dataSource type="POOLED">
    <property name="driver" value="${jdbc_driver}"/>
    <property name="url" value="${jdbc_url}"/>
    <property name="username" value="${jdbc_username}"/>
    <property name="password" value="${jdbc_password}"/>
    </dataSource>
    </environment>
    </environments>
    <mappers>
    <mapper class="com.bones.mapper.EmpMapper"/>
    </mappers>
    </configuration>

    其中mappers标签中书写mapper映射文件的路径(这里用的是基于接口的代理模式开发

    typeAliases标签为实体类的包com.bones.pojo取了别名,为了在mapper映射文件中可以直接写实体类的别名(Emp—>emp,一般为小写)

    EmpMapper.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

    <mapper namespace="com.bones.mapper.EmpMapper">

    </mapper>

    sql语句会逐渐丰富在这里面。

    【3】准备实体类:

    现在有的数据库表是:

    mybatis-动态SQL

    结合lombok准备实体类代码:

    package com.bones.pojo;

    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;

    import java.io.Serializable;
    import java.util.Date;

    /**
    * @author : bones
    * @version : 1.0
    */


    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Emp implements Serializable {
    private Integer empno;
    private String ename;
    private String job;
    private Integer mgr;
    private Date hiredate;
    private Double sal;
    private Double comm;
    private Integer deptno;
    }

    if标签

    现在有数据库表Emp,根据用户传入的值进行查询(用户可能不会给出所有属性的所有的值)

    mybatis-动态SQL

    接口EmpMapper

    package com.bones.mapper;

    import com.bones.pojo.Emp;

    import java.util.List;

    /**
    * @author : bones
    * @version : 1.0
    */

    public interface EmpMapper {
    List<Emp> findByCondition(Emp emp);
    }

    映射文件EmpMapper.xml:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

    <mapper namespace="com.bones.mapper.EmpMapper">
    <!-- List<Emp> findByCondition(Emp emp);-->
    <select id="findByCondition" resultType="emp">
    select * from emp where 1=1
    <if test="empno != null">
    and empno = #{empno}
    </if>
    <if test="ename != null and ename != ''">
    and ename = #{ename}
    </if>
    <if test="job != null and job != ''">
    and ename = #{ename}
    </if>
    <if test="mgr != null">
    and mgr = #{mgr}
    </if>
    <if test="hiredate != null">
    and hiredate = #{hiredate}
    </if>
    <if test="sal != null">
    and sal = #{sal}
    </if>
    <if test="comm != null">
    and comm = #{comm}
    </if>
    <if test="deptno != null">
    and deptno = #{deptno}
    </if>
    </select>
    </mapper>

    测试代码:(junit测试)

    package com.bones.test;

    import com.bones.mapper.EmpMapper;
    import com.bones.pojo.Emp;
    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 org.junit.After;
    import org.junit.Before;
    import org.junit.Test;

    import java.io.IOException;
    import java.io.InputStream;
    import java.util.List;

    /**
    * @author : bones
    * @version : 1.0
    */

    public class Test01 {
    SqlSession sqlSession;
    @Before
    public void init(){
    SqlSessionFactoryBuilder ssfb = new SqlSessionFactoryBuilder();
    InputStream resourceAsStream = null;
    try {
    resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    } catch (IOException e) {
    e.printStackTrace();
    }
    SqlSessionFactory factory = ssfb.build(resourceAsStream);
    sqlSession = factory.openSession();

    }

    @Test
    public void testFindByCondition(){
    EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
    Emp emp = new Emp(null, null, null, null, null, null, null, null);
    List<Emp> empList = empMapper.findByCondition(emp);
    empList.forEach(System.out::println);
    }

    @After
    public void close(){
    sqlSession.close();
    }
    }

    注意几点:

    1.实体类尽量使用包装类作为数据类型,因为要判空。

    2.lombok注解 的使用注意:

    Lombok使⽤@Data可以⽣成类⾥⾯所有属性的getter/setter⽅法和无参构造方法。

    比如我现在只是写了注解:@Data然后利用maven的生命周期,先clean再compile,查看target目录下的 字节码文件:

    mybatis-动态SQL

    其实除了类⾥⾯所有属性的getter/setter⽅法和无参构造方法之外,还有equals,canEqual(相当于instanceof),hashCode,toString等方法。

    但是如果注解中加入了@Data@AllArgsConstructor两个注解,奇妙的事发生了:

    mybatis-动态SQL

    这里@Data注解并没有生成无参构造方法。

    所以要在这种情况下使用无参构造器,是不可能 的。

    mybatis-动态SQL

    而很多框架都会调用无参构造方法去创建对象,所以无参构造方法是必须 的。

    此时必须显式声明出无参构造方法 ,即 加上注解@NoArgsConstructor.

    还要注意:我最近读到一篇文章https://mp.weixin.qq.com/s/wWoQQgVrJSjRVAg-oPQidw【Lombok 同时使用 @Data 和 @Builder 的巨坑,千万别乱用!】

    在那篇文章中说Lombok同时使⽤@Data@Builder ,构建无参构造器报错,其实也是上面我说的问题:如果同时使⽤@Data@Builder的话,@Data在有其他注解(@Builder@AllArgsConstructor这两个注解)尽管⽣成了GET/SET⽅法,但是⽆参构造⽅法没有了,这显然是不能接受的.

    于是我想到了,在我的实体类中不用 @AllArgsConstructor而用@Builder试试呢,但是compile就报错了:

    mybatis-动态SQL

    无法将类 com.bones.pojo.Emp中的构造器 Emp应用到给定类型;

    请注意:我这时候加的是三个注解:

    mybatis-动态SQL

    报错到底是为什么呢?

    @Builder注解默认用的是全参数构造函数,此时会导致无法new无参对象,为了解决这个问题往往会在@Builder注解的类上加上@NoArgsConstructor注解或者手动加上无参构造函数,此时虽然可以new无参对象了,但却会报上面这个错误。

    原因就是在类上贴了@NoArgsConstructor注解,此时只会生成无参构造器。

    解决:解决方法很简单,只需在使用@Builder注解的类上再加上@AllArgsConstructor注解即可.

    此时重新编译,不仅没有报错,而且查看字节码文件,发现还多了一个内部静态类

    mybatis-动态SQL
    image-20221015130325095

    3.关于查询,也可以设置模糊查询,以ename(员工姓名)字段为例:

    非模糊查询:
    <if test="ename != null and ename != ''">
    and ename = #{ename}
    </if>
    模糊查询:(比如查询名称中带有"A")
    <if test="ename != null and ename != ''">
    and ename like concat('%',#{ename},'%')
    </if>

    测试:

    @Test
    public void testFindByCondition(){
    EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
    Emp emp = new Emp(null, "LL", null, null, null, null, null, null);
    List<Emp> empList = empMapper.findByCondition(emp);
    empList.forEach(System.out::println);
    }

    查询结果:

    mybatis-动态SQL
    image-20221015163535789

    4.where 1=1这个技巧所带来的好处,后面的每一个if标签都需要带上and 再加条件就可以了。否则对于语句 的拼接将比较麻烦。

    5.在测试方法中,什么参数都不传的情况之下,将查询所有数据:

    Emp emp = new Emp();
    List<Emp> empList = empMapper.findByCondition(emp);

    此时通过打印的日志可以看到查询的语句是

    DEBUG - ==>  Preparing: select * from emp where 1=1

    where标签

    在所有的if标签之外套上where标签,并且sql语句不需要再写where 1=1

    select * from emp 
    <where>
    <if test="empno != null">
    and empno = #{empno}
    </if>
    .......
    .......
    .......
    <if test="deptno != null">
    and deptno = #{deptno}
    </if>
    </where>

    where标签解决的就是where 1=1的问题,在没有查询条件的时候,就不再拼接where语句了,在有多个查询的语句的时候,会拼接上,同时会解决多余或者缺少的and。

    mybatis-动态SQL
    image-20221015165209226
    mybatis-动态SQL
    image-20221015165301874

    when标签和choose标签

    where标签搭配使用

    接口EmpMapper

    List<Emp> findByCondition2(Emp emp);

    Mapper映射文件:

    <select id="findByCondition2" resultType="emp">
    select * from emp
    <where>
    <choose>
    <when test="empno != null">
    and empno = #{empno}
    </when>
    <when test="ename != null and ename != ''">
    and ename like concat('%',#{ename},'%')
    </when>
    <when test
    ="job != null and job != ''">
    and ename = #{ename}
    </when>
    <when test="mgr != null">
    and mgr = #{mgr}
    </when>
    <when test="hiredate != null">
    and hiredate = #{hiredate}
    </when>
    <when test="sal != null">
    and sal = #{sal}
    </when>
    <when test="comm != null">
    and comm = #{comm}
    </when>
    <when test="deptno != null">
    and deptno = #{deptno}
    </when>
    </choose>
    </where>
    </select>

    if不同的是:if是多个条件都会查,而choose-when标签只会根据顺序查询一个。

    比如:

    @Test
    public void testFindByCondition2(){
    EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
    Emp emp = new Emp();
    emp.setEname("A");
    emp.setDeptno(30);
    List<Emp> empList = empMapper.findByCondition2(emp);
    empList.forEach(System.out::println);
    }

    现在查询名字中包含A,部门号是30的员工,日志中打印的SQL语句是:

    DEBUG - ==>  Preparing: select * from emp WHERE ename like concat('%',?,'%')

    也就是说,choose标签中的when只要满足一个就不再往下判断了,写的越前面,查询的优先级越高。

    实际到底用if多还是choose-when用的多?看实际需求。

    set标签

    前面的几个标签是针对于数据库 的查询,下面介绍更改。

    现在接口中的抽象方法是:

    int updateByCondition(Emp emp);

    完成的功能是根据传入的emp对象,按照员工编号empno更改相关数据,如果传入的是null就说明不需要修改这个字段。

    利用set标签完成更改数据:(因为是update标签,所以不需要写returnType和ParameterType)

    Mapper映射文件内容:

    <!--    int updateByCondition(Emp emp);-->
    <update id="updateByCondition">
    update emp
    <set>
    <if test="ename != null and ename != ''">
    , ename = #{ename}
    </if>
    <if test="job != null">
    , job = #{job}
    </if>
    <if test="mgr != null">
    , mgr = #{mgr}
    </if>
    <if test="hiredate != null">
    , hiredate = #{hiredate}
    </if>
    <if test="sal != null">
    , sal = #{sal}
    </if>
    <if test="comm != null">
    , comm = #{comm}
    </if>
    <if test="deptno != null">
    , deptno = #{deptno}
    </if>
    </set>
    where empno = #{empno}
    </update>

    测试方法:

    @Test
    public void testUpdateByCondition(){
    EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
    Emp emp = new Emp();
    emp.setEmpno(7654);
    emp.setDeptno(10);
    empMapper.updateByCondition(emp);
    //提交事务
    sqlSession.commit();
    }

    注意:

    1.要提交事务,否则数据库不会显示更新的数据

    2.一定要传参数empno,否则没法根据empno更改数据

    3.要在每个条件之前加上逗号,否则报错。

    trim标签

    set标签和where标签可以看作是trim标签的特例。

    将之前提到过的set标签和where标签做相应的修改,改为trim标签

    where标签

    <select id="findByCondition" resultType="emp">
    select * from emp
    <where>
    <if test="empno != null">
    and empno = #{empno}
    </if>
    ......
    <if test="deptno != null">
    and deptno = #{deptno}
    </if>
    </where>
    </select>

    改为trim标签:

    <select id="findByCondition3" resultType="emp">
    select * from emp
    <trim prefix="where" prefixOverrides="and">
    <if test="empno != null">
    and empno = #{empno}
    </if>
    ......
    <if test="deptno != null">
    and deptno = #{deptno}
    </if>
    </trim>
    </select>

    set标签

    <update id="updateByCondition">
    update emp
    <set>
    <if test="ename != null and ename != ''">
    , ename = #{ename}
    </if>
    ......
    <if test="deptno != null">
    , deptno = #{deptno}
    </if>
    </set>
    where empno = #{empno}
    </update>

    改为trim标签

    <update id="updateByCondition2" >
    update emp
    <trim prefix="set" prefixOverrides=",">
    <if test="ename != null and ename != ''">
    , ename = #{ename}
    </if>
    <if test="job != null">
    , job = #{job}
    </if>
    <if test="mgr != null">
    , mgr = #{mgr}
    </if>
    <if test="hiredate != null">
    , hiredate = #{hiredate}
    </if>
    <if test="sal != null">
    , sal = #{sal}
    </if>
    <if test="comm != null">
    , comm = #{comm}
    </if>
    <if test="deptno != null">
    , deptno = #{deptno}
    </if>
    </trim>
    where empno = #{empno}
    </update>

    trim 标签有几个属性:

    prefix:要添加的前缀

    prefixOverrides:添加前缀之后,需要动态修改的内容(比如加上set之后,就要求参数之间用,连接,但是第一个参数跟在set之后,又不需要,连接)

    suffix:要添加的前缀

    suffixOverrides:添加后缀之后,需要动态修改的内容(与prefixOverrides类似)

    bind标签

    这个标签和模糊查询有关。

    首先回顾在mybatis中,不用bind标签如何书写模糊查询:

    <if test="ename != null and ename != ''">
    and ename like concat('%',#{ename},'%')
    </if>

    用的是concat 进行参数的拼接。

    现在改为用bind标签进行处理:

    <if test="ename != null and ename != ''">
    <bind name="enamePattern" value="'%'+ename+'%'"/>
    and ename like #{enamePattern}
    </if>

    bind一般用于处理模糊查询的模板.

    sql标签

    在写纯的sql语句的时候时常会碰到相同的部分,比如对于emp表进行操作:

    select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp where ename like ....
    select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp where empno = ... and ...
    insert into emp (empno,ename,job,mgr,hiredate,sal,comm,deptno) values(......)

    发现其实有很多内容是反复使用的。

    那么可不可以因此“偷个懒”呢?在mybatis中答案当然是可以的!

    下面以之前几个案例,利用sql标签来完善(改善)一下EmpMapper.xml文件

    首先将需要反复利用的部分抽成sql标签中的内容:

    <sql id="empColumn">empno,ename,job,mgr,hiredate,sal,comm,deptno</sql>
    <sql id="empBaseSelect">select <include refid="empColumn"/> from emp</sql>

    在上面的例子中已经看出了,如何使用sql中反复出现的标签内容,那就是用include标签。而且在sql标签中可以嵌套include标签。然后修改EmpMapper.xml中的sql查询语句:

    EmpMapper.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

    <mapper namespace="com.bones.mapper.EmpMapper">
    <sql id="empColumn">empno,ename,job,mgr,hiredate,sal,comm,deptno</sql>
    <sql id="empBaseSelect">select <include refid="empColumn"/> from emp</sql>
    <!-- List<Emp> findByCondition(Emp emp);-->
    <select id="findByCondition" resultType="emp">
    <include refid="empBaseSelect"/>
    <where>
    <if test="empno != null">
    and empno = #{empno}
    </if>
    .....
    </where>
    </select>
    <!-- List<Emp> findByCondition2(Emp emp);-->
    <select id="findByCondition2" resultType="emp">
    <include refid="empBaseSelect"/>
    <where>
    <choose>
    <when test="empno != null">
    and empno = #{empno}
    </when>
    ......
    </choose>
    </where>
    </select>

    <!-- List<Emp> findByCondition3(Emp emp);-->
    <select id="findByCondition3" resultType="emp">
    <include refid="empBaseSelect"/>
    <trim prefix="where" prefixOverrides="and">
    <if test="empno != null">
    and empno = #{empno}
    </if>
    ......
    </trim>
    </select>
    <!-- int updateByCondition(Emp emp);-->
    <update id="updateByCondition">
    update emp
    <set>
    <if test="ename != null and ename != ''">
    , ename = #{ename}
    </if>
    ......
    </set>
    where empno = #{empno}
    </update>
    <!-- int updateByCondition2(Emp emp);-->
    <update id="updateByCondition2" >
    update emp
    <trim prefix="set" prefixOverrides=",">
    <if test="ename != null and ename != ''">
    , ename = #{ename}
    </if>
    ......
    </trim>
    where empno = #{empno}
    </update>
    </mapper>

    实际应用中,这个标签十分有用,因为这个标签可以实现一处修改,处处有效:

    在实际开发中会遇到许多相同的SQL,比如根据某个条件筛选,这个筛选很多地方都能用到,我们可以将其抽取出来成为一个公用的部分,这样修改也方便,一旦出现了错误,只需要改这一处便能处处生效了,此时就用到了<sql>这个标签了。

    当多种类型的查询语句的查询字段或者查询条件相同时,可以将其定义为常量,方便调用。为求 <select> 结构清晰也可将 sql 语句分解。

    foreach标签

    这个标签类似于java语法中的增强for循环:

    for (Emp emp1 : empList) {

    }

    在sql中使用的场景是in语句的查询。

    比如现在的数据库表emp:

    mybatis-动态SQL
    image-20221015210712888

    想要查询工资是2000,2975,950的人,但是下一次要查询工资是1500,1250的人,如果用sql语句来写可以是:

    select * from emp where SAL  in(2000,2975,950);
    select * from emp where SAL  in(1500,1250);

    如果用动态SQL该如何完成呢?

    首先可以用数组/List/Map/Set来装要查询的数据。

    准备Mapper接口的抽象方法:

    List<Emp> findBySal1(iDouble[] sals);
    List<Emp> findBySal2(List<Double> sals);

    在映射文件中完善两个抽象方法:

    <!--        List<Emp> findBySal1(int[] sals);-->
    <select id="findBySal1" resultType="emp">
    <include refid="empBaseSelect"/> where sal in
    <foreach collection="array" separator="," open="(" close=")" item="sal">
    #{sal}
    </foreach>
    </select>



    <!-- List<Emp> findBySal2(List<Double> sals);-->
    <select id="findBySal2" resultType="emp">
    <include refid="empBaseSelect"/> where sal in
    <foreach collection="list" separator="," open="(" close=")" item="sal">
    #{sal}
    </foreach>
    </select>

    测试方法:

    @Test
    public void testFindBySals(){
    EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
    List<Emp> empList = empMapper.findBySal1(new Double[]{2000.0,2975.0,950.0});
    System.out.println("findBySals1-----array");
    empList.forEach(System.out::println);
    }
    @Test
    public void testFindBySals2(){
    EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
    List<Double> list = new ArrayList<>();
    Collections.addAll(list,1500.0,1250.0);
    List<Emp> empList = empMapper.findBySal2(list);
    System.out.println("findBySals1-----list");
    empList.forEach(System.out::println);
    }

    3.浅析动态SQL的执行原理

    其执行原理为,使用 OGNL 的表达式,从 SQL 参数对象中计算表达式的值,根据表达式的值动态拼接 SQL ,以此来完成动态 SQL 的功能。



原文始发于微信公众号(小东方不败):mybatis-动态SQL

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/47430.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!