官方描述
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
在实际工作中,我们会遇到很多比较复杂的业务,需要构建更为复杂的 SQL 语句,比如多条件组合查询、in 比较等。为此 Mybatis 提供了一套标签来解决我们的问题,其中包括 if, choose, when, otherwise, trim, where, set, foreach等标签 ,方便我们构建安全可靠的 SQL 语句,提高开发效率。
1、创建一个 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 '浏览量' ) ENGINE=InnoDB DEFAULT CHARSET=utf8 复制代码
2、创建一个 Maven 项目
3、IDutils 工具类,用来生成 UUID,作为博客 id 唯一值。
public class IDutils { public static String getId(){ return UUID.randomUUID().toString().replace("-",""); } } 复制代码
4、MybatisUtil 工具类,获取 SqlSession
public class MybatisUtil { private static SqlSessionFactory sqlSessionFactory; static { String resource = "mybatis-config.xml"; try { InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(true); } } 复制代码
5、Blog.java
@Data @AllArgsConstructor @NoArgsConstructor public class Blog { private String id; private String title; private String author; private Date createTime; private int view; } 复制代码
6、编写 Mapper 相关文件
public interface BlogMapper { int addBlog(Blog blog); } 复制代码
<?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.msdn.dao.BlogMapper"> <insert id="addBlog" parameterType="Blog"> insert into mybatis.blog(id, title, author, create_time, view) values (#{id}, #{title}, #{author}, #{createTime}, #{view}) </insert> </mapper> 复制代码
7、db.properties
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&useSSL=true&serverTimezone=UTC&characterEncoding=utf-8 username=root password=1234567 复制代码
8、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" /> <typeAliases> <package name="com.msdn.bean"/> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <!--jdbc.url=jdbc:mysql://localhost:3306/oto?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC--> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="com/msdn/dao/BlogMapper.xml"/> </mappers> </configuration> 复制代码
9、新增数据
@Test public void add(){ SqlSession sqlSession = MybatisUtil.getSqlSession(); BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class); Blog blog = new Blog(); blog.setId(IDutils.getId()); blog.setAuthor("hresh"); blog.setTitle("Spring IoC学习系列一"); blog.setCreateTime(new Date()); blog.setView(1234); blogMapper.addBlog(blog); blog.setId(IDutils.getId()); blog.setTitle("Spring IoC学习系列二"); blogMapper.addBlog(blog); blog.setId(IDutils.getId()); blog.setTitle("Spring IoC学习系列三"); blogMapper.addBlog(blog); sqlSession.close(); } 复制代码
根据作者名和博客标题进行查询,如果某一条件为空,则只根据另一条件进行查询。
1、编写接口类
List<Blog> selectByIf(Map<String,Object> map); 复制代码
2、Mapper 配置文件
<select id="selectByIf" parameterType="map" resultType="blog"> select * from mybatis.blog where 1=1 <if test="title != null"> and title = #{title} </if> <if test="author != null"> and author = #{author} </if> </select> 复制代码
3、测试
@Test public void select(){ SqlSession sqlSession = MybatisUtil.getSqlSession(); BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class); HashMap<String, Object> m = new HashMap<String, Object>(); // m.put("title","Spring IoC学习系列一"); m.put("author","hresh2"); List<Blog> blogs = blogMapper.selectByIf(m); for (Blog blog : blogs) { System.out.println(blog); } sqlSession.close(); } 复制代码
其中 m 集合中可以存放键值对,也可以为空,执行都不会报错。
修改上述的 SQL 语句:
<select id="selectByIf" parameterType="map" resultType="blog"> select * from mybatis.blog <where> <if test="title != null"> and title = #{title} </if> <if test="author != null"> and author = #{author} </if> </where> </select> 复制代码
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”, where 元素也会将它们去除。
1、编写接口方法
int update(Map<String,Object> map); 复制代码
2、Mapper 配置文件
<update id="update" parameterType="map"> update mybatis.blog <set> <if test="title != null"> title = #{title}, </if> <if test="author != null"> author = #{author} </if> </set> where id = #{id} </update> 复制代码
3、测试
@Test public void updateTest(){ SqlSession sqlSession = MybatisUtil.getSqlSession(); BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class); HashMap<String, Object> m = new HashMap<String, Object>(); m.put("title","Spring IoC学习系列1"); // m.put("author","hresh2"); m.put("id","8b42c64435a047458d2867fad347e8d6"); blogMapper.update(m); sqlSession.close(); } 复制代码
当 SQL 语句中的 author 条件不成立时,set 标签会将 title 条件后带的逗号自动去除掉。
有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句。
1、编写接口方法
List<Blog> queryByChoose(Map<String,Object> map); 复制代码
2、Mapper 配置文件
xml <select id="queryByChoose" parameterType="map" resultType="blog"> select * from mybatis.blog where 1=1 <choose> <when test="title != null"> and title = #{title} </when> <when test="author != null"> and author = #{author} </when> <otherwise> and id = #{id} </otherwise> </choose> </select>
3、测试
@Test public void queryByChoose(){ SqlSession sqlSession = MybatisUtil.getSqlSession(); BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class); HashMap<String, Object> m = new HashMap<String, Object>(); m.put("title","Spring IoC学习系列1"); m.put("author","hresh22"); m.put("id","8b42c64435a047458d2867fad347e8d6"); List<Blog> blogs = blogMapper.queryByChoose(m); for (Blog blog : blogs) { System.out.println(blog); } sqlSession.close(); } 复制代码
当 title 属性值不为空时,SQL 语句则执行 title = #{title}
条件,其他条件不予处理。
在 if 语句中如果条件过多,导致 SQL 语句变得臃肿,为了增加代码的重用性,可以考虑将部分代码提取出来,提高复用性。
同 if 语句中的案例效果一致,我们换用一种实现方式。
<sql id="wherSql" > <if test="title != null"> and title = #{title} </if> <if test="author != null"> and author = #{author} </if> </sql> <select id="selectByIf2" parameterType="map" resultType="blog"> select * from mybatis.blog <where> <!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace --> <include refid="wherSql"></include> </where> </select> 复制代码
测试
@Test public void select(){ SqlSession sqlSession = MybatisUtil.getSqlSession(); BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class); HashMap<String, Object> m = new HashMap<String, Object>(); m.put("title","Spring IoC学习系列1"); // m.put("author","hresh2"); // List<Blog> blogs = blogMapper.selectByIf(m); List<Blog> blogs = blogMapper.selectByIf2(m); for (Blog blog : blogs) { System.out.println(blog); } sqlSession.close(); } 复制代码
结果为:
2020-03-20 23:40:58,135 DEBUG [com.msdn.dao.BlogMapper.selectByIf2] - ==> Preparing: select * from mybatis.blog WHERE title = ? 2020-03-20 23:40:58,169 DEBUG [com.msdn.dao.BlogMapper.selectByIf2] - ==> Parameters: Spring IoC学习系列1(String) 2020-03-20 23:40:58,190 DEBUG [com.msdn.dao.BlogMapper.selectByIf2] - <== Total: 1 Blog(id=8b42c64435a047458d2867fad347e8d6, title=Spring IoC学习系列1, author=hresh2, createTime=Wed Mar 04 19:49:04 CST 2020, view=1234) 复制代码
注意:
提取的 SQL 片段中的 and 连接符会被自动处理掉。
如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR "> ... </trim> 复制代码
Mapper 配置文件
<select id="queryByChoose2" parameterType="map" resultType="blog"> select * from mybatis.blog <trim prefix="where" prefixOverrides="and |or"> <if test="title != null"> title = #{title} </if> <if test="author != null"> and author = #{author} </if> </trim> </select> 复制代码
测试
@Test public void queryByChoose(){ SqlSession sqlSession = MybatisUtil.getSqlSession(); BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class); HashMap<String, Object> m = new HashMap<String, Object>(); // m.put("title","Spring IoC学习系列1"); m.put("author","hresh2"); List<Blog> blogs = blogMapper.queryByChoose2(m); for (Blog blog : blogs) { System.out.println(blog); } sqlSession.close(); } 复制代码
prefixOverrides 属性会忽略通过管道符分隔的文本序列。
1、编写接口方法
List<Blog> queryByForeach(Map<String,Object> map); 复制代码
2、Mapper 配置文件
<select id="queryByForeach" parameterType="map" resultType="blog"> select * from mybatis.blog <where> <!-- collection:指定输入对象中的集合属性 item:每次遍历生成的对象 open:开始遍历时的拼接字符串 close:结束时拼接的字符串 separator:遍历对象之间需要拼接的字符串 select * from blog where 1=1 and (id=1 or id=2 or id=3) --> <foreach collection="authors" item="author" open="(" close=")" separator="or"> author = #{author} </foreach> </where> </select> 复制代码
3、测试
@Test public void queryByForeach(){ SqlSession sqlSession = MybatisUtil.getSqlSession(); BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class); HashMap<String, Object> m = new HashMap<String, Object>(); List<String> authors = new ArrayList<String>(); authors.add("hresh2"); authors.add("hresh"); m.put("authors",authors); List<Blog> blogs = blogMapper.queryByForeach(m); for (Blog blog : blogs) { System.out.println(blog); } sqlSession.close(); } 复制代码
当 authors 列表为空时,执行该代码将查询出所有的结果集。
MyBatis:动态SQL
动态 SQL