10.动态 SQL
动态 SQL 就是根据不同的条件生成不同的 SQL 语句
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
1.搭建环境
1.1 SQL
DROP TABLE IF EXISTS `blog`;
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 '浏览量',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `blog` VALUES ('0001', 'hong/My-Note', '谢禹宏', '2021-04-21 00:00:00', '100');
INSERT INTO `blog` VALUES ('0002', 'MyBatis-Plus', '苞米豆', '2021-04-21 00:00:00', '100');
INSERT INTO `blog` VALUES ('0003', 'Hello MyBatis', '母鸡', '2021-04-21 00:00:00', '120');
INSERT INTO `blog` VALUES ('0004', 'Hello Vuew', '尤雨溪', '2021-01-21 00:00:00', '100');
INSERT INTO `blog` VALUES ('0005', 'Hello Linux', '林纳斯', '2001-04-21 00:00:00', '120');
1.2 实体类
package org.hong.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.sql.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Blog {
private String id;
private String title;
private String author;
private Date createTime;
private Integer views;
}
1.3 MyBatisUtil 工具类
package org.hong.util;
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;
public class MyBatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
// 获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
// 既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
// SqlSession 提供了在数据库执行 SQL 命令所需的所有方法
public static SqlSession getSqlSession(){
// openSession(): 此方式打开SQL会话, 事务是开启状态
// openSession(true): 此方式打开SQL会话, 事务是关闭状态
return sqlSessionFactory.openSession();
}
public static SqlSessionFactory getSqlSessionFactory() {
return sqlSessionFactory;
}
}
1.4 jdbc.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?userSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=1234
1.5 log4j.properties
log4j.rootLogger=DEBUG,A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=[%t] [%c]-[%p] %m%n
1.6 MyBatis 核心配置文件
<?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: 核心配置文件 -->
<configuration>
<!-- 导入外部配置文件, 放在最前面 -->
<properties resource="jdbc.properties"/>
<settings>
<!-- 设置日志输出, 方便观察sql语句和参数 -->
<setting name="logImpl" value="LOG4J"/>
<!-- 开启驼峰命名法 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!--
environments配置项目的运行环境, 可以配置多个
default: 启用的环境
-->
<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>
<!-- 每一个Mapper.xml都需要在MyBatis核心配置文件中注册!!! -->
<mappers>
<package name="org.hong.mapper"/>
</mappers>
</configuration>
1.7 BlogMapper.java
package org.hong.mapper;
public interface BlogMapper {
}
1.8 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="org.hong.mapper.BlogMapper">
</mapper>
2.if
2.1 接口方法
// 查询博客, 携带了哪个字段查询条件就带上这个字段的值
List<Blog> getByBlog(Blog blog);
2.2 方法映射
<!-- if -->
<select id="getByBlog" resultType="org.hong.pojo.Blog">
select * from blog where
<!-- test: 判断的表达式 (OGNL)-->
<if test="title != null and title.trim() != ''">
title like #{title}
</if>
<if test="author != null and author.trim() != ''">
and author like #{author}
</if>
</select>
2.3 测试用例
@Test
public void testIf(){
SqlSession sqlSession = MyBatisUtil.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Blog blog = new Blog();
blog.setTitle("%MyBatis%");
// 这里赋值一个空字符串
blog.setAuthor("");
List<Blog> list = mapper.getByBlog(blog);
list.forEach(System.out :: println);
sqlSession.commit();
sqlSession.close();
}
2.4 控制台打印
## 发送的sql并没有带上author字段
[main] [org.hong.mapper.BlogMapper.getByBlog]-[DEBUG] ==> Preparing: select * from blog where title like ?
[main] [org.hong.mapper.BlogMapper.getByBlog]-[DEBUG] ==> Parameters: %MyBatis%(String)
[main] [org.hong.mapper.BlogMapper.getByBlog]-[DEBUG] <== Total: 2
Blog(id=0002, title=MyBatis-Plus, author=苞米豆, createTime=2021-04-21, views=100)
Blog(id=0003, title=Hello MyBatis, author=母鸡, createTime=2021-04-21, views=120)
2.5 问题
如果不给 title 赋值,SQL 就会变成这样:select * from user where and author like #{author}
这样的 SQL 明显是非法的,如何解决?
解决方案:
- 在 where 添加后面添加 1=1, 以后的条件都
and xxx``(不推荐, 会导致数据库性损失)
- MyBatis 使用
where
标签来将所有的查询条件包括在内,MyBatis 会**自动的忽略 where 后第一个不合法的 and 或 or**
, 并且在**有条件的情况下自动拼接上 where**
3.where
Mapper 接口和测试不变,对 if
演示的方法映射进行改造!!!
3.1 方法映射
<!-- where -->
<select id="getByBlog" resultType="org.hong.pojo.Blog">
select * from blog
<where>
<if test="title != null and title.trim() != ''">
and title like #{title}
</if>
<if test="author != null and author.trim() != ''">
and author like #{author}
</if>
</where>
</select>
3.2 测试用例
@Test
public void testWhere(){
SqlSession sqlSession = MyBatisUtil.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Blog blog = new Blog();
blog.setTitle("");
blog.setAuthor("%谢%");
List<Blog> list = mapper.getByBlog(blog);
list.forEach(System.out :: println);
sqlSession.commit();
sqlSession.close();
}
3.3 控制台打印
## MyBatis帮我们加上了where并且忽略了where后面第一个不合法的and或or
[main] [org.hong.mapper.BlogMapper.getByBlog]-[DEBUG] ==> Preparing: select * from blog WHERE author like ?
[main] [org.hong.mapper.BlogMapper.getByBlog]-[DEBUG] ==> Parameters: %谢%(String)
[main] [org.hong.mapper.BlogMapper.getByBlog]-[DEBUG] <== Total: 1
Blog(id=0001, title=hong/My-Note, author=谢禹宏, createTime=2020-01-01, views=100)
3.4 问题
我们进行模糊查询时,每次给属性赋值都加上了 %%
,显示的加上通配符,这样并不是很好,应该让 MyBatis 为我们加上通配符,想要完成这个功能需要使用 bind
元素。
4.bind
元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文。通常用来拼接模糊查询
Mapper 接口和测试不变,对 where
演示的方法映射进行改造!!!
4.1 方法映射
<select id="getByBlog" resultType="org.hong.pojo.Blog">
select * from blog
<where>
<if test="title != null and title.trim() != ''">
<!-- bind -->
<bind name="newTitle" value="'%' + title + '%'"/>
and title like #{newTitle}
</if>
<if test="author != null and author.trim() != ''">
<bind name="newAuthor" value="'%' + author + '%'"/>
and author like #{newAuthor}
</if>
</where>
</select>
4.2 测试用例
@Test
public void testBind(){
SqlSession sqlSession = MyBatisUtil.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Blog blog = new Blog();
// 直接赋值了一个y, 没有使用通配符
blog.setTitle("y");
List<Blog> list = mapper.getByBlog(blog);
list.forEach(System.out :: println);
sqlSession.commit();
sqlSession.close();
}
4.3 控制台打印
[main] [org.hong.mapper.BlogMapper.getByBlog]-[DEBUG] ==> Preparing: select * from blog WHERE title like ?
## MyBatis为sql语句赋值的参数, 可以发现y的左右两边加上了%通配符
[main] [org.hong.mapper.BlogMapper.getByBlog]-[DEBUG] ==> Parameters: %y%(String)
[main] [org.hong.mapper.BlogMapper.getByBlog]-[DEBUG] <== Total: 3
Blog(id=0001, title=hong/My-Note, author=谢禹宏, createTime=2020-01-01, views=100)
Blog(id=0002, title=MyBatis-Plus, author=苞米豆, createTime=2021-04-21, views=100)
Blog(id=0003, title=Hello MyBatis, author=母鸡, createTime=2021-04-21, views=120)
到这里,我们的 getByBlog
方法就变得很优雅了!!!
5.set
set
元素会动态地在行首插入 SET
关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
5.1 接口方法
// 修改, 但是只修改属性值不为null的属性
int update(Blog blog);
5.2 方法映射
<!-- set -->
<update id="update">
update blog
<set>
<if test="title != null and title.trim() != ''">
, title = #{title}
</if>
<if test="author != null and author.trim() != ''">
, author = #{author}
</if>
<if test="createTime != null">
, create_time = #{createTime}
</if>
<if test="views != null and views > 0">
, views = #{views}
</if>
</set>
where id = #{id}
</update>
5.3 测试用例
@Test
public void testSet(){
SqlSession sqlSession = MyBatisUtil.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Blog blog = new Blog();
blog.setId("0001");
blog.setCreateTime(Date.valueOf("2020-1-1"));
int update = mapper.update(blog);
sqlSession.commit();
sqlSession.close();
}
5.4 控制台打印
## mybatis帮我们加上了set关键字, 并且删除了set后面第一个不合法的逗号
[main] [org.hong.mapper.BlogMapper.update]-[DEBUG] ==> Preparing: update blog SET create_time = ? where id = ?
[main] [org.hong.mapper.BlogMapper.update]-[DEBUG] ==> Parameters: 2020-01-01(Date), 0001(String)
[main] [org.hong.mapper.BlogMapper.update]-[DEBUG] <== Updates: 1
6.choose{when
, otherwise
}
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。(choose --> switch,when --> case,otherwise --> default)
6.1 接口方法
// 有id, 根据id精准匹配; 有title就根据title进行模糊查询; 如果都没有就查询author为苞米豆的blog
List<Blog> getByBlogChoose(Blog blog);
6.2 方法映射
<select id="getByBlogChoose" resultType="org.hong.pojo.Blog">
select * from blog
<where>
<choose>
<when test="id != null and id > 0">
and id = #{id}
</when>
<when test="title != null and title.trim() != ''">
<bind name="title" value="'%' + title + '%'"/>
and title like #{title}
</when>
<otherwise>
and author = '苞米豆'
</otherwise>
</choose>
</where>
</select>
6.3 测试用例
@Test
public void testChoose(){
SqlSession sqlSession = MyBatisUtil.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
// 赋值 id 和 title
Blog blog1 = new Blog();
blog1.setId("0001");
blog1.setTitle("my");
List<Blog> list1 = mapper.getByBlogChoose(blog1);
System.out.println(list1);
// 只赋值 title
Blog blog2 = new Blog();
blog2.setTitle("my");
List<Blog> list2 = mapper.getByBlogChoose(blog2);
System.out.println(list2);
// 什么都不赋值
List<Blog> list3 = mapper.getByBlogChoose(null);
System.out.println(list3);
sqlSession.commit();
sqlSession.close();
}
6.4 控制台打印
## 给 id 和 title 属性赋值, 因为choose只会进入一个hwen, 所以查询条件只有id, 没有title
[main] [org.hong.mapper.BlogMapper.getByBlogChoose]-[DEBUG] ==> Preparing: select * from blog WHERE id = ?
[main] [org.hong.mapper.BlogMapper.getByBlogChoose]-[DEBUG] ==> Parameters: 0001(String)
[main] [org.hong.mapper.BlogMapper.getByBlogChoose]-[DEBUG] <== Total: 1
[Blog(id=0001, title=hong/My-Note, author=谢禹宏, createTime=2020-01-01, views=100)]
## 只给 title 属性赋值, 查询条件加上了title
[main] [org.hong.mapper.BlogMapper.getByBlogChoose]-[DEBUG] ==> Preparing: select * from blog WHERE title like ?
[main] [org.hong.mapper.BlogMapper.getByBlogChoose]-[DEBUG] ==> Parameters: %my%(String)
[main] [org.hong.mapper.BlogMapper.getByBlogChoose]-[DEBUG] <== Total: 3
[Blog(id=0001, title=hong/My-Note, author=谢禹宏, createTime=2020-01-01, views=100), Blog(id=0002, title=MyBatis-Plus, author=苞米豆, createTime=2021-04-21, views=100), Blog(id=0003, title=Hello MyBatis, author=母鸡, createTime=2021-04-21, views=120)]
## 什么都没给, 传了一个null, 进入了otherwise中
[main] [org.hong.mapper.BlogMapper.getByBlogChoose]-[DEBUG] ==> Preparing: select * from blog WHERE author = '苞米豆'
[main] [org.hong.mapper.BlogMapper.getByBlogChoose]-[DEBUG] ==> Parameters:
[main] [org.hong.mapper.BlogMapper.getByBlogChoose]-[DEBUG] <== Total: 1
[Blog(id=0002, title=MyBatis-Plus, author=苞米豆, createTime=2021-04-21, views=100)]
7.foreach
7.1 接口方法
// 通过id的List集合查询多条数据
List<Blog> getByIds(List<String> ids);
7.2 方法映射
<select id="getByIds" resultType="org.hong.pojo.Blog">
select * from blog
<where>
<if test="list != null and list.size() > 0">
<!--
collection: 指定遍历的集合; 只能写与集合类型对应的名字,如果想使用其他名称则必须使用@param注解指定名称
item: 将当前遍历的元素赋值给指定的变量
separator: 元素之间的分隔符
open: 指定开始符号
close: 指定结束符号
index: 遍历List的时候是index索引, item是当前值
遍历Map的时候index是map的key, item是map的值
-->
<foreach collection="collection" item="id" open="and id in(" separator="," close=")">
#{id}
</foreach>
</if>
</where>
</select>
7.3 测试用例
@Test
public void testForeach(){
SqlSession sqlSession = MyBatisUtil.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
String []ids = new String[] {"0001", "0002", "0003"};
List<Blog> list = mapper.getByIds(Arrays.asList(ids));
list.forEach(System.out :: println);
sqlSession.commit();
sqlSession.close();
}
7.4 控制台打印
[main] [org.hong.mapper.BlogMapper.getByIds]-[DEBUG] ==> Preparing: select * from blog WHERE id in( ? , ? , ? )
[main] [org.hong.mapper.BlogMapper.getByIds]-[DEBUG] ==> Parameters: 0001(String), 0002(String), 0003(String)
[main] [org.hong.mapper.BlogMapper.getByIds]-[DEBUG] <== Total: 3
Blog(id=0001, title=hong/My-Note, author=谢禹宏, createTime=2020-01-01, views=100)
Blog(id=0002, title=MyBatis-Plus, author=苞米豆, createTime=2021-04-21, views=100)
Blog(id=0003, title=Hello MyBatis, author=母鸡, createTime=2021-04-21, views=120)
8.Sql (抽取可重用 SQL 片段)
场景:在真实开发中我们不能写这样的 SQL 语句 select * from xxx
,是不能写 *
的,但是每写一个查询语句都写上全部的列名,就造成了代码的冗余,而且也不易于维护。还好 MyBatis 提供了解决方案。如果表中字段发生了更改,我们只需要修改 sql
片段就 OK 了。
8.1 接口方法
List<Blog> getAll();
8.2 方法映射
<!--
sql: 抽取片段
id: 唯一标识
-->
<sql id="column">
id, title, author, create_time, views
</sql>
<select id="getAll" resultType="org.hong.pojo.Blog">
<!--
include: 引入sql节点定义的sql片段
refid: 引用指定id的sql片段
-->
select <include refid="column"/> from blog
</select>
8.3 测试用例
@Test
public void testSql(){
SqlSession sqlSession = MyBatisUtil.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
List<Blog> list = mapper.getAll();
list.forEach(System.out :: println);
sqlSession.commit();
sqlSession.close();
}
8.4 控制台打印
## mybatis为我们拼接上了列名
[main] [org.hong.mapper.BlogMapper.getAll]-[DEBUG] ==> Preparing: select id, title, author, create_time, views from blog
[main] [org.hong.mapper.BlogMapper.getAll]-[DEBUG] ==> Parameters:
[main] [org.hong.mapper.BlogMapper.getAll]-[DEBUG] <== Total: 5
Blog(id=0001, title=hong/My-Note, author=谢禹宏, createTime=2020-01-01, views=100)
Blog(id=0002, title=MyBatis-Plus, author=苞米豆, createTime=2021-04-21, views=100)
Blog(id=0003, title=Hello MyBatis, author=母鸡, createTime=2021-04-21, views=120)
Blog(id=0004, title=Hello Vuew, author=尤雨溪, createTime=2021-01-21, views=100)
Blog(id=0005, title=Hello Linux, author=林纳斯, createTime=2001-04-21, views=120)