MyBatis代码实例系列-06:Mybatis动态SQL标签(一)---if、where、set、trim、choose

超级通道:MyBatis代码实例系列-绪论

本章主要记录MyBatis动态SQL的标签,涉及到的知识点有:
1. MyBatis动态SQL标签:用于降低根据不同条件拼接 SQL 语句的难度。
2. MyJsonResult:通过泛型定义通用返回类型,实现复用。

本章介绍的动态SQL的标签有:if、where、set、trim、choose,下面对这些标签进行简单说明:
- if标签:用于处理SQL语句中where条件部分可以确定、部分不能确定的情况。
- where标签:用于处理SQL语句中where条件都不可以确定的情况。
- set:用于处理SQL(update)语句中,更新字段不能确定的情况。
- trim:提供了前缀和后缀的智能消除功能,可以用于替代where和set标签,其实有更加强大的用法。
- choose:一般与WHEN和OTHERWISE标签合作,用于处理多种条件只其一的情况,类似于switch。

1.业务场景与SQL

本次主要以书籍为业务对象进行各类实例操作,其SQL如下:

drop table book;

create table `book`(
    `id` int(4) unsigned not null comment '书id',
    `name` varchar(20) not null comment '书名',
    `number` varchar(8) not null comment '书编号',
    `price` int(3) not null comment '价格',
    `old` int(1) not null comment '是否二手书',
    `chinese` int(1) not null comment '是否汉化书',
    primary key(id)
)engine=InnoDB comment='书籍' default charset=utf8;

select * from book;

insert into book values(1,'初中数学1','CZ-SX-01',12,1,1);
insert into book values(2,'初中数学2','CZ-SX-02',22,1,0);
insert into book values(3,'初中数学3','CZ-SX-03',22,0,1);
insert into book values(4,'初中数学4','CZ-SX-04',13,0,0);
insert into book values(5,'高中数学1','GZ-SX-01',33,0,0);
insert into book values(6,'高中数学2','GZ-SX-02',9,0,1);
insert into book values(7,'高中数学3','GZ-SX-03',7,1,0);
insert into book values(8,'高中数学4','GZ-SX-04',12,1,1);
insert into book values(9,'大学英语1','DX-YW-01',33,0,0);
insert into book values(10,'大学英语2','DX-YW-01',22,0,1);
insert into book values(11,'大学英语3','DX-YW-01',20,1,0);
insert into book values(12,'大学英语4','DX-YW-01',40,1,1);

2.目录结构

src
\---main
    \---java
    |   \---pers
    |       \---hanchao
    |           \---himybatis
    |               |---dynamic
    |                    \---Book.java
    |                    |---IBookDAO.java
    |                    |---BookController.java
    |                    |---BookService.java
    |                    |---MyJsonResult.java
    \---resources
    |   \---mybatis-mappers
    |   |   \---Book.xml
    |   |---log4j.properties
    |   |---jdbc.properties
    |   |---applicationContext.xml
    |   |---spring-mybatis-jdbc.xml
    \---webapp
        \---dynamic
        |   \---book.jsp
        |---WEB-INF
        |   \---web.xml
        |   |---spring-mvc-servlet.xml
        |---index.jsp

3.XML映射文件:Book.xml

数据库操作主要集中在XML映射文件中。

3.1.何为动态SQL–以if标签为例

场景:查询所有的旧书,查询条件可能有价格、是否汉化
常规SQL(#表示不确定)如下:

        SELECT * FROM `book`
        where old = 1
        # AND price <= ''
        # AND chinese = ''

其中后面两个条件是不确定的,因为无法确定参数中price和chinese是否有值。

手动拼接
如果不通过MyBatis等框架而是手动编程,可能就需要进行一系列的判断,然后根据不同的情况,拼接SQL语句。
在Java中,可能是这么编码的:

StringBuffer sb = new StringBuffer(" SELECT * FROM `book` where old = 1 ");
//如果price不为空,则追加price条件
if(null != book.getNumber()){//如果前端无值,则Integer转换成null
    sb.append(" AND price <= ").append(book.getPrice()).append("");
}
//如果chinese不为空,则追加chinese条件
if ("".equals(book.getChinese())){//如果前端无值,则String转换成''
    sb.append(" AND chinese = '").append(book.getPrice()).append("'");
}
String sql = sb.toString();

上述是最轻松的情形,还可能面对以下问题:

  • 拼接语句时,忘了注意空格,导致出错。如:
sb.append("SELECT * FROM book").append("where old = 1");
//--->SELECT * FROM bookwhere old = 1
  • 拼接语句时,忘了AND和OR,导致出错。如:
sb.append("SELECT * FROM book where").append(" AND old = 1");
//--->SELECT * FROM book where AND old = 1
  • 拼接语句时,忘了逗号,导致出错。如:
sb.append("UPDATE book set ").append(" price = 1,").append(" chinese= 1,");
//--->UPDATE book set price = 1, chinese= 1,

其实还有可能遇到其他各类问题,这里不再多例举。

MyBatis动态拼接

通过MyBatis的动态SQL标签,可以自动的解决大部分问题。如上面的业务场景,只需要通过一个if标签即可解决。如下:

<!--查询所有的旧书,查询条件可能有价格、是否汉化-->
<select id="selectBookWitif" parameterType="Book" resultType="Book">
    SELECT * FROM `book`
    where old = 1
    <if test="price != null">
        AND price &lt;= #{price}
    </if>
    <if test="chinese != null">
        AND chinese = #{chinese}
    </if>
</select>

动态SQL标签都会自动在拼接的SQL语句中间添加空格,避免错误。

下面依次介绍其他标签。

3.2.where标签

where标签用于解决所有的查询条件都不确定的情形。

业务情景:查询所有书籍,查询条件可能有价格、是否汉化

普通SQL(#表示不确定):

SELECT * FROM `book`
# where
# price &gt;= #{price}
# AND chinese = #{chinese}

MyBatis动态SQL:

where标签会智能的判断是否插入where语句,并且自动消除第一个紧随其后的AND。

<!--查询所有书籍,查询条件可能有价格、是否汉化【where形式】-->
<select id="selectBookWithwhere" parameterType="Book" resultType="Book">
    SELECT * FROM `book`
    <where>
        <if test="price != null">
            price &gt;= #{price}
        </if>
        <if test="chinese != null">
            AND chinese = #{chinese}
        </if>
    </where>
</select>

注意:每个if标签都可以开头追加AND,不过建议第一个if不追加AND。

3.3.set标签

set标签用于解决所有的更新字段都不确定的情形。

业务情景:根据id修改一本书,可能的修改字段有价格、书籍名

普通SQL(#表示不确定):

UPDATE `book`
# set
# price = #{price},
# name = #{name}
where id = #{id}

MyBatis动态SQL:

set标签会智能的判断是否插入set语句,并且自动消除最后一个逗号。

<!--根据id修改一本书,可能的修改条件有价格、书籍名【set形式】-->
<update id="updateBookWithset" parameterType="Book">
    UPDATE `book`
    <set>
        <if test="price != null">
            price = #{price},
        </if>
        <if test="name != ''">
            name = #{name}
        </if>
    </set>
    <where>
        id = #{id}
    </where>
</update>

注意:每个if标签都可以在结尾追加逗号,不过建议最后一个不追加逗号。

3.4.trim标签

trim标签会智能的判断是否插入prefix或者suffix语句,并且自动消除第一个紧随其后的prefixOverrides或者最后一个suffixOverrides,能够用来替代where标签和set标签

3.4.1.替代where

业务情景:根据id修改一本书,可能的修改条件有价格、书籍名

trim标签会智能的判断是否插入prefix(“where”),并且自动消除第一个紧随其后的prefixOverrides(“AND|OR”)

<!--查询所有书籍,查询条件可能有价格、是否汉化【trim形式】-->
<select id="selectBookWithtrim" resultType="Book" parameterType="Book">
    SELECT * FROM `book`
    <trim prefix="where" prefixOverrides="AND|OR">
        <if test="price != null">
            price &gt;= #{price}
        </if>
        <if test="chinese != null">
            AND chinese = #{chinese}
        </if>
    </trim>
</select>
3.4.2.替代set

业务情景:根据id修改一本书,可能的修改条件有价格、书籍名

trim标签会智能的判断是否插入prefix(“set”),并且自动消除最后一个的suffixOverrides(“,”)

<!--根据id修改一本书,可能的修改条件有价格、书籍名【trim形式】-->
<update id="updateBookWithtrim" parameterType="Book">
    UPDATE `book`
    <trim prefix="set" suffixOverrides=",">
        <if test="price != null">
            price = #{price},
        </if>
        <if test="name != ''">
            name = #{name}
        </if>
    </trim>
    <where>
        id = #{id}
    </where>
</update>

3.5.choose标签

choose标签用于解决所有的查询条件或者更新字段只取其一的情形。

业务情景:查询书籍,如果查询条件有编号按编号查询,有书名就按书名查询,有价格按价格查询,都没有就查询旧书

普通SQL(#表示不确定):

SELECT * FROM `book`
# WHERE number = #{number}
# WHERE name = #{name}
# WHERE price = #{price}
# WHERE old = TRUE

MyBatis动态SQL:

choose标签依照书写顺序依次检查when条件,当有一个when满足时,不会再进行后续when条件的判断;但所有的when条件都不满足时,会进入otherwise进行处理。

<!--查询书籍,如果查询条件有编号按编号查询,有书名就按书名查询,有价格按价格查询,都没有就查询旧书-->
<select id="selectBookWithChoose" parameterType="Book" resultType="Book">
    SELECT * FROM `book`
    <choose>
        <when test="number != ''">
            WHERE number = #{number}
        </when>
        <when test="name != ''">
            WHERE name = #{name}
        </when>
        <when test="price != null">
            WHERE price = #{price}
        </when>
        <otherwise>
            WHERE old = TRUE
        </otherwise>
    </choose>
</select>

完整的Book.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">
<!--MyBatis的分配置文件,分别对应每个实体,用来配置SQL操作及SQL语句-->
<!--namespace,定义这个映射的命名域,这里指向Dao层接口-->
<mapper namespace="pers.hanchao.himybatis.dynamic.IBookDAO">
    <!--Integer的空值形式是null,String的空值形式是''-->

    <!--根据id查询一本书-->
    <select id="selectBookById" resultType="Book" parameterType="Integer">
        SELECT * FROM `book` WHERE id = #{id}
    </select>

    <!--查询所有的旧书,查询条件可能有价格、是否汉化-->
    <select id="selectBookWitIf" parameterType="Book" resultType="Book">
        SELECT * FROM `book`
        WHERE old = 1
        <if test="price != null">
            AND price &lt;= #{price}
        </if>
        <if test="chinese != null">
            AND chinese = #{chinese}
        </if>
    </select>

    <!--查询所有书籍,查询条件可能有价格、是否汉化【where形式】-->
    <select id="selectBookWithWhere" parameterType="Book" resultType="Book">
        SELECT * FROM `book`
        <where>
            <if test="price != null">
                price &gt;= #{price}
            </if>
            <if test="chinese != null">
                AND chinese = #{chinese}
            </if>
        </where>
    </select>
    <!--查询所有书籍,查询条件可能有价格、是否汉化【trim形式】-->
    <select id="selectBookWithTrim" resultType="Book" parameterType="Book">
        SELECT * FROM `book`
        <trim prefix="WHERE" prefixOverrides="AND|OR">
            <if test="price != null">
                price &gt;= #{price}
            </if>
            <if test="chinese != null">
                AND chinese = #{chinese}
            </if>
        </trim>
    </select>

    <!--根据id修改一本书,可能的修改条件有价格、书籍名【set形式】-->
    <update id="updateBookWithSet" parameterType="Book">
        UPDATE `book`
        <set>
            <if test="price != null">
                price = #{price},
            </if>
            <if test="name != ''">
                name = #{name}
            </if>
        </set>
        <where>
            id = #{id}
        </where>
    </update>
    <!--根据id修改一本书,可能的修改条件有价格、书籍名【trim形式】-->
    <update id="updateBookWithTrim" parameterType="Book">
        UPDATE `book`
        <trim prefix="SET" suffixOverrides=",">
            <if test="price != null">
                price = #{price},
            </if>
            <if test="name != ''">
                name = #{name}
            </if>
        </trim>
        <where>
            id = #{id}
        </where>
    </update>

    <!--查询书籍,如果查询条件有编号按编号查询,有书名就按书名查询,有价格按价格查询,都没有就查询旧书-->
    <select id="selectBookWithChoose" parameterType="Book" resultType="Book">
        SELECT * FROM `book`
        <choose>
            <when test="number != ''">
                WHERE number = #{number}
            </when>
            <when test="name != ''">
                WHERE name = #{name}
            </when>
            <when test="price != null">
                WHERE price = #{price}
            </when>
            <otherwise>
                WHERE old = TRUE
            </otherwise>
        </choose>
    </select>
</mapper>

4.其他文件

4.1.实体类Book.java

package pers.hanchao.himybatis.dynamic;

/**
 * <p>书籍</p>
 * @author hanchao 2018/1/30 22:46
 **/
public class Book {
    /** 书籍id */
    private Integer id;
    /** 书籍名称 */
    private String name;
    /** 书籍编号 */
    private String number;
    /** 价钱 */
    private Integer price;
    /** 是否二手书 */
    private Integer old;
    /** 是否汉化书 */
    private Integer chinese;

    //constructor toString setter getter
}

4.2.业务控制类:BookController.java

package pers.hanchao.himybatis.dynamic;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <p>MyBatis动态SQL:If,Where,Set,Trim,Choose,ForEach,Bind</p>
 * @author hanchao 2018/1/31 22:46
 **/
@RestController
public class BookController {
    private static final Logger LOGGER = Logger.getLogger(BookController.class);

    @Autowired
    private BookService bookService;

    /**
     * <p>查询所有的旧书,查询条件可能有价格、是否汉化</p>
     * @author hanchao 2018/1/31 22:52
     **/
    @PostMapping("/book/if")
    public MyJsonResult<Book> selectBookWithIf(@RequestBody Book book){
        StringBuffer sb = new StringBuffer(" SELECT * FROM `book` WHERE old = 1 ");
        //如果price不为空,则追加price条件
        if(null != book.getNumber()){//如果前端无值,则Integer转换成null
            sb.append(" AND price <= ").append(book.getPrice()).append("");
        }
        //如果chinese不为空,则追加chinese条件
        if ("".equals(book.getChinese())){//如果前端无值,则String转换成''
            sb.append(" AND chinese = '").append(book.getPrice()).append("'");
        }
        String sql = sb.toString();

        LOGGER.info(book);
        List<Book> bookList = this.bookService.selectBookWithIf(book);
        if (null == bookList || bookList.size() == 0){
            return new MyJsonResult<Book>(2,"暂无记录!");
        }else {
            return new MyJsonResult<Book>(1,"success!",bookList);
        }


    }
    /**
     * <p>查询所有书籍,查询条件可能有价格、是否汉化【where形式】</p>
     * @author hanchao 2018/1/31 22:53
     **/
    @PostMapping("/book/where")
    public MyJsonResult<Book> selectBookWithWhere(@RequestBody Book book){
        LOGGER.info(book);
        List<Book> bookList = this.bookService.selectBookWithWhere(book);
        if (null == bookList || bookList.size() == 0){
            return new MyJsonResult<Book>(2,"暂无记录!");
        }else {
            return new MyJsonResult<Book>(1,"success!",bookList);
        }
    }

    /**
     * <p>查询所有书籍,查询条件可能有价格、是否汉化【trim形式】</p>
     * @author hanchao 2018/1/31 22:54
     **/
    @PostMapping("/book/trim/where")
    public MyJsonResult<Book> selectBookWithTrim(@RequestBody Book book){
        LOGGER.info(book);
        List<Book> bookList = this.bookService.selectBookWithTrim(book);
        if (null == bookList || bookList.size() == 0){
            return new MyJsonResult<Book>(2,"暂无记录!");
        }else {
            return new MyJsonResult<Book>(1,"success!",bookList);
        }
    }

    /**
     * <p>根据id修改一本书,可能的修改条件有价格、书籍名【set形式】</p>
     * @author hanchao 2018/1/31 22:54
     **/
    @PostMapping("/book/set")
    public MyJsonResult<Book> updateBookWithSet(@RequestBody Book book){
        LOGGER.info(book);
        //先查书籍是否存在
        Book book1 = this.bookService.selectBookById(book.getId());
        if (null == book1){
            return new MyJsonResult<Book>(2,"此书不存在,无法修改!");
        }else {
            this.bookService.updateBookWithSet(book);
            return new MyJsonResult<Book>();
        }
    }

    /**
     * <p>根据id修改一本书,可能的修改条件有价格、书籍名【trim形式】</p>
     * @author hanchao 2018/1/31 22:54
     **/
    @PostMapping("/book/trim/set")
    public MyJsonResult<Book> updateBookWithTrim(@RequestBody Book book){
        LOGGER.info(book);
        //先查书籍是否存在
        Book book1 = this.bookService.selectBookById(book.getId());
        if (null == book1){
            return new MyJsonResult<Book>(2,"此书不存在,无法修改!");
        }else {
            this.bookService.updateBookWithTrim(book);
            return new MyJsonResult<Book>();
        }
    }

    /**
     * <p>查询书籍,如果查询条件有编号按编号查询,有书名就按书名查询,有价格按价格查询,都没有就查询旧书</p>
     * @author hanchao 2018/1/31 23:08
     **/
    @PostMapping("/book/choose")
    public MyJsonResult<Book> updateBookWithChoose(@RequestBody Book book){
        LOGGER.info(book);
        List<Book> bookList = this.bookService.updateBookWithChoose(book);
        if (null == bookList || bookList.size() == 0){
            return new MyJsonResult<Book>(2,"暂无记录!");
        }else {
            return new MyJsonResult<Book>(1,"success!",bookList);
        }
    }
}

4.3.DAO层接口类:IBookDAO.java

package pers.hanchao.himybatis.dynamic;

import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Map;

/**
 * <p>书籍类的Dao层接口</p>
 * @author hanchao 2018/1/30 22:47
 **/
@Repository
public interface IBookDAO {
    /** 根据id查询一本书 */
    Book selectBookById(Integer id);

    /** 查询所有的旧书,查询条件可能有价格、是否汉化 */
    List<Book> selectBookWitIf(Book book);

    /** 查询所有书籍,查询条件可能有价格、是否汉化【where形式】 */
    List<Book> selectBookWithWhere(Book book);

    /** 查询所有书籍,查询条件可能有价格、是否汉化【trim形式】 */
    List<Book> selectBookWithTrim(Book book);

    /** 根据id修改一本书,可能的修改条件有价格、书籍名【set形式】 */
    void updateBookWithSet(Book book);

    /** 根据id修改一本书,可能的修改条件有价格、书籍名【trim形式】 */
    void updateBookWithTrim(Book book);

    /** 查询书籍,如果查询条件有编号按编号查询,有书名就按书名查询,有价格按价格查询,都没有就查询旧书 */
    List<Book> selectBookWithChoose(Book book);
}

4.4.返回封装类:MyJsonResult.java

package pers.hanchao.himybatis.dynamic;

import java.util.List;

/**
 * <p>Json返回值</p>
 * @author hanchao 2018/1/31 22:44
 **/
public class MyJsonResult<E> {
    /** 状态码 */
    private Integer code = 1;
    /** 消息 */
    private String message = "success!";
    /** 数据 */
    private List<E> data;

    public void setCodeAndMessage(Integer code,String message){
        this.code = code;
        this.message = message;
    }
    //constructor toString setter getter
}

4.5.前台页面:book.jsp

<%--
  Created by IntelliJ IDEA.
  User: hanchao
  Date: 2018/1/31
  Time: 23:13
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>书籍管理</title>
    <style type="text/css">
        div{margin: 5px 5px; }
        .div-left {float: left;margin: 5px 5px;border:1px solid #96c2f1; background:#eff7ff;}
        .div-text-center {text-align:center; border:1px solid #96c2f1; background:#eff7ff;width: 300px;}
        textarea{border:1px solid #9bdf70;background:#f0fbeb}
    </style>
</head>
<body>
<div class="div-left div-text-center">
    <textarea id="show" style="width: 300px;height: 575px;"></textarea>
</div>
<div class="div-left">
    <div class="div-text-center">
        <label>书籍主键:</label><input type="text" id="id" name="id"/><hr/>
        <label>书籍编号:</label><input type="text" id="number" name="number"/><hr/>
        <label>书籍名称:</label><input type="text" id="name" name="name"/><hr/>
        <label>书籍价格:</label><input type="text" id="price" name="price"/><hr/>
        <label>是否旧书:</label><input type="text" id="old" name="old"/><hr/>
        <label>是否汉化:</label><input type="text" id="chinese" name="chinese"/><hr/>
    </div>
    <div>
        <input type="button" value="selectBookWithIf" onclick="book('if')"/><hr/>
        <input type="button" value="selectBookWithWhere" onclick="book('where')"/>
        <input type="button" value="selectBookWithTrim" onclick="book('trim/where')"/><hr/>
        <input type="button" value="selectBookWithChoose" onclick="book('choose')"/><hr/>
        <input type="button" value="updateBookWithSet" onclick="book('set')"/>
        <input type="button" value="updateBookWithTrim" onclick="book('trim/set')"/><hr/>
    </div>
</div>
</body>
<script type="text/javascript" src="../static/jquery-3.2.1.min.js"></script>
<script type="text/javascript">
    function book(url_suffix) {
        $.ajax({
            type:'POST',
            url:'/book/' + url_suffix,
            data:JSON.stringify({
                id:$('#id').val(),
                number:$('#number').val(),
                name:$('#name').val(),
                price:$('#price').val(),
                old:$('#old').val(),
                chinese:$('#chinese').val()
            }),
            contentType:'application/json;charset=utf-8',
            success:function (data) {
                console.log(data);
                var html = "url:/book/" + url_suffix + "\nresult:\n" + JSON.stringify(data,null,4);
                $("#show").html(html)
            },
            error:function (data) {
                alert(data.method);
            }
        });
    }
</script>
</html>

5.result

5.1.if标签

情景:查询所有的旧书,查询条件可能有价格、是否汉化
这里写图片描述

5.2.where标签与trim标签

情景:查询所有书籍,查询条件可能有价格、是否汉化
这里写图片描述

5.3.set标签与trim标签

情景:根据id修改一本书,可能的修改条件有价格、书籍名
这里写图片描述

5.4.choose标签

情景:查询书籍,如果查询条件有编号按编号查询,有书名就按书名查询,有价格按价格查询,都没有就查询旧书
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值