闲话
最近对象跟我说,她把我写进论文最后的致谢里了,没有想过这么浪漫的事也会发生在我的身上
基本要点
定义:动态SQL可以理解为,通过使用一系列标签,去动态生成我们所需要的SQL语句
1、IF标签
首先我们创建一个表,里面存放博客相关信息
DROP TABLE IF EXISTS `t_decade_blog`;
create table t_decade_blog
(
`id` VARCHAR(50) NOT NULL COMMENT '博客id',
`name` VARCHAR(50) NOT NULL COMMENT '博客名称',
`author` VARCHAR(50) NOT NULL COMMENT '博客作者',
`create_time` datetime NOT NULL COMMENT '创建时间',
`views` INT(20) NOT NULL COMMENT '博客浏览量'
)ENGINE = INNODB CHARACTER SET = utf8;
 
接着我们创建对应的Java实体类
package com.decade.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Blog {
    private String id;
    private String name;
    private String author;
    private Date createTime;
    private int views;
}
 
接着我们创建接口类和对应的SQL映射文件
package com.decade.mapper;
import com.decade.entity.Blog;
import java.util.List;
import java.util.Map;
public interface BlogMapper {
    int addBlog(Blog blog);
    
    List<Blog> queryBlogByIf(Map map);
}
 
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.decade.mapper.BlogMapper">
    <insert id="addBlog" parameterType="Blog">
        insert into t_decade_blog (id,name,author,create_time,views)
        values (#{id},#{name},#{author},#{createTime},#{views})
    </insert>
    <select id="queryBlogByIf" parameterType="map" resultType="Blog">
        select * from t_decade_blog
        where author = 'Decade0712'
        <if test="views != null">
            and views= #{views}
        </if>
        <if test="name != null">
            and name = #{name}
        </if>
    </select>
</mapper>
 
另外,我们还有工具类IdUtils,用于创建随机id
package com.decade.utils;
import java.util.UUID;
public class IdUtils {
    public static String getId() {
        return UUID.randomUUID().toString();
    }
}
 
和Mybatis创建sqlSession的工具类
package com.decade.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;
/**
 * Mybatis工具类
 */
public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory;
    static {
        try {
            // 使用Mybatis第一步:获取sqlSession
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    // 既然有了sqlSessionFactory,我们就可以得到sqlSession实例了
    // sqlSession完全包含了面向数据库执行sql命令所需要的方法,如果要开启自动提交事务,openSession中设置true即可
    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession(true);
    }
}
 
接着是核心配置文件Mybatis-config.xml
<?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">
        <property name="username" value="decade"/>
        <property name="password" value="11111"/>
    </properties>
    
    <settings>
        <!-- 标准的日志工厂 -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <typeAliases>
        <package name="com.decade.entity"/>
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!--驱动配置,com.mysql.jdbc.driver -->
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--每一个mapper.xml文件都需要在核心配置文件中注册-->
    <mappers>
        <mapper class="com.decade.mapper.BlogMapper"/>
    </mappers>
</configuration>
 
和数据库信息文件db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/decade_test?useUnicode=true&characterEncoding=UTF-8
username=root
password=root
 
最后我们创建两个测试方法,一个插入数据,另外一个测试if标签
import com.decade.entity.Blog;
import com.decade.mapper.BlogMapper;
import com.decade.utils.IdUtils;
import com.decade.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MyTest {
    @Test
    public void test() {
        Blog blog = new Blog(IdUtils.getId(), "Mybatis系列", "Decade0712", new Date(), 2000);
        Blog blog1 = new Blog(IdUtils.getId(), "Spring系列", "Decade0712", new Date(), 4000);
        Blog blog2 = new Blog(IdUtils.getId(), "设计模式系列", "Decade0712", new Date(), 6000);
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        try {
            BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
            mapper.addBlog(blog);
            mapper.addBlog(blog1);
            mapper.addBlog(blog2);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
    }
    @Test
    public void test2() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        try {
            BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
            Map<String, Object> map = new HashMap<>();
            map.put("name", "Spring系列");
            map.put("views", 4000);
            List<Blog> blogs = mapper.queryBlogByIf(map);
            blogs.forEach(System.out::println);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
    }
}
 
按照预期,如果我们传入空的map,那么会查出所有的数据,否则查出符合条件的数据
运行结果如下
 
2、choose标签
MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句
 只要满足其中一个就退出判断了,后面的就不会继续判断了
 otherwise等价于default,如果上述条件都不满足,就走otherwise下的条件语句
<select id="queryBlogByWhich" parameterType="map" resultType="Blog">
	select * from t_decade_blog where create_time = '2022-03-28 22:30:14'
    <choose>
	    <when test="views != null">
        	and views = #{views}
        </when>
        <when test="name != null">
	        and name = #{name}
        </when>
	    <otherwise>
            and author = 'Decade0712'
	    </otherwise>
    </choose>
</select>
 
3、where标签
在我们使用if标签时,有可能写成这样
<select id="queryBlogInfo" parameterType="map" resultType="Blog">
  SELECT * FROM BLOG
  WHERE
  <if test="views != null">
	  views= #{views}
  </if>
  <if test="name != null">
  	  and name = #{name}
  </if>
</select>
 
如果上述条件都不满足,那么查询sql就会变成
SELECT * FROM BLOG WHERE
 
如果只匹配第二个条件,那么sql会变成,也会查询失败
SELECT * FROM BLOG
WHERE
AND name = 'decade'
 
上述两种情况均会导致查询失败
为了避免这种情况发生,我们通常将where标签和if标签搭配使用,类似于
<select id="queryBlogByWhere" parameterType="map" resultType="Blog">
	select * from t_decade_blog
    <where>
	    <if test="views != null">
	        and views= #{views}
        </if>
        <if test="name != null">
            and name = #{name}
        </if>
   </where>
</select>
 
where 标签只会在满足条件的情况下才插入 “WHERE” 关键字,如果第一个条件是以 “AND” 或 “OR”为开头,where 元素也会自动将它们去除,比如这里,如果views不为空,那么where会自动将这里的and去掉,最后执行的查询语句是
SELECT * FROM BLOG
WHERE
views = 'decade'
 
4、set标签
set标签一般用于代替sql语句中的set关键字
<update id="updateBlog" parameterType="map">
	update t_decade_blog
    <set>
	    <if test="name != null">name=#{name},</if>
        <if test="author != null">author=#{author},</if>
        <if test="views != null">views=#{views}</if>
    </set>
    where id = #{id}
</update>
 
和where标签异曲同工的是,它会自动帮你去掉多余的逗号
 就像上面,如果只满足第一个if,它会自动删除name后面的逗号
 最后执行的sql为
update t_decade_blog set name = 'aaa' where id = 'id'
 
5、trim标签
可以理解为,我们可以使用trim标签来自己决定如何拼接sql
参数解释如下
prefix:在trim标签内sql语句加上前缀。
prefixOverrides:指定去除多余的前缀内容 
如:prefixOverrides = "and",去除trim标签内sql语句多余的前缀"and"。
suffix:在trim标签内sql语句加上后缀。
suffixOverrides:指定去除多余的后缀内容
如:suffixOverrides=",",去除trim标签内sql语句多余的后缀","
 
这里,我们在sql前面加上(前缀,以)为后缀结尾,并且去掉多余的逗号
<insert id="insert" parameterType="map">
	INSERT INTO t_decade_blog 
    <trim prefix="(" suffix=")" suffixOverrides=",">
        id,name,author,create_time,views
    </trim>
    <trim prefix="VALUES(" suffix=")" suffixOverrides=",">
        #{id},#{name}, #{author}, #{createTime},#{views}
    </trim>
</insert>
 
如果 where 标签与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 标签的功能。比如,和 where 标签等价的自定义 trim 元素为
<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>
 
同理,set标签也可以用trim去定义
<trim prefix="SET" suffixOverrides=",">
  ...
</trim>
 
6、foreach标签
- foreach 元素的功能非常强大,你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach
 - 它也允许你指定开头(open)与结尾(close)的字符串以及集合项迭代之间的分隔符(separator)
 - 当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素
 - 当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值
 
我们结合一个例子来学习一下,假设我们想用spring系列和mybatis系列的id去查一下博客信息
 用SQL应该是
select * from t_decade_blog where id in ('76782763-48d0-4cef-b8e1-1054e181e41d','06bd6c69-e47e-4e78-9e02-5110a013e457');
 
现在,我们用mybatis中的foreach标签实现一下
 首先我们在SQL映射文件中定义一个查询语句,这里要遍历的集合叫ids,每一次遍历得到的元素叫id,我们以(开头,用逗号做分隔符,以)结尾
<select id="queryBlogByForeach" parameterType="map" resultType="Blog">
	select * from t_decade_blog where id in
    <foreach collection="ids" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</select>
 
然后我们定义一下接口类
package com.decade.mapper;
import com.decade.entity.Blog;
import java.util.List;
import java.util.Map;
public interface BlogMapper {
    List<Blog> queryBlogByForeach(Map map);
}
 
最后我们写一下测试方法
import com.decade.entity.Blog;
import com.decade.mapper.BlogMapper;
import com.decade.utils.IdUtils;
import com.decade.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.*;
public class MyTest {
    @Test
    public void test2() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        try {
            BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
            Map<String, Object> map4 = new HashMap<>();
            List<String> ids = new ArrayList<>();
            ids.add("06bd6c69-e47e-4e78-9e02-5110a013e457");
            ids.add("76782763-48d0-4cef-b8e1-1054e181e41d");
            map4.put("ids", ids);
            List<Blog> blogs4 = mapper.queryBlogByForeach(map4);
            blogs4.forEach(System.out::println);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
    }
}
 
运行结果如下,成功查询出我们需要的数据
 
7、SQL片段
我们可以将重复出现的SQL部分提取出来
注意:
 最好基于单表来定义sql片段,不要有太复杂的查询(例如多表查询)
 不要存在where标签
<select id="queryBlogByWhere" parameterType="map" resultType="Blog">
	select * from t_decade_blog
    <where>
	    <if test="views != null">
	        and views= #{views}
        </if>
        <if test="name != null">
            and name = #{name}
        </if>
   </where>
</select>
 
上方的查询可以抽取为
<sql id="queryByIf">
	<if test="views != null">
		and views= #{views}
    </if>
    <if test="name != null">
        and name = #{name}
    </if>
</sql>
<select id="queryBlogByWhere" parameterType="map" resultType="Blog">
	select * from t_decade_blog
    <where>
	    <include refid="queryByIf"/>
   </where>
</select>
 
如有错误,欢迎指正!!










