MyBatis动态SQL(认真看看, 以后写SQL就爽多了)

0 一起来学习 mybatis

你想不想来学习 mybatis? 学习其使用和源码呢?那么, 在博客园关注我吧!!

我自己打算把这个源码系列更新完毕, 同时会更新相应的注释。快去 star 吧!!

MyBatis 令人喜欢的一大特性就是动态 SQL。 在使用 JDBC 的过程中, 根据条件进行 SQL 的拼接是很麻烦且很容易出错的。 MyBatis 动态 SQL 的出现, 解决了这个麻烦。

MyBatis通过 OGNL 来进行动态 SQL 的使用的。

目前, 动态 SQL 支持以下几种标签

元素作用备注

if判断语句单条件分支

choose(when、otherwise)相当于 Java 中的 if else多条件分支

trim(where、set)辅助元素用于处理 SQL 拼接问题

foreach循环语句批量插入, 更新, 查询时经常用到

bind创建一个变量, 并绑定到上下文中用于兼容不同的数据库, 防止 SQL 注入等

1 数据准备

为了后面的演示, 创建了一个 Maven 项目 mybatis-dynamic, 创建了对应的数据库和表

DROPTABLEIFEXISTS`student`;

CREATETABLE`student`(

`student_id`int(10)unsignedNOTNULLAUTO_INCREMENTCOMMENT'编号',

`name`varchar(20)DEFAULTNULLCOMMENT'姓名',

`phone`varchar(20)DEFAULTNULLCOMMENT'电话',

`email`varchar(50)DEFAULTNULLCOMMENT'邮箱',

`sex`tinyint(4)DEFAULTNULLCOMMENT'性别',

`locked`tinyint(4)DEFAULTNULLCOMMENT'状态(0:正常,1:锁定)',

`gmt_created`datetimeDEFAULTCURRENT_TIMESTAMPCOMMENT'存入数据库的时间',

`gmt_modified`datetimeDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPCOMMENT'修改的时间',

`delete`int(11)DEFAULTNULL,

PRIMARYKEY(`student_id`)

)ENGINE=InnoDBAUTO_INCREMENT=7DEFAULTCHARSET=utf8mb4COLLATE=utf8mb4_0900_ai_ciCOMMENT='学生表';

对应的项目结构

2 if 标签

if 标签是我们最常使用的。 在查询、删除、更新的时候很可能会使用到。 必须结合 test 属性联合使用。

2.1 在 WHERE 条件中使用 if 标签

这是常见的一种现象, 我们在进行按条件查询的时候, 可能会有多种情况。

2.1.1 查询条件

根据输入的学生信息进行条件检索

当只输入用户名时, 使用用户名进行模糊检索;

当只输入性别时, 使用性别进行完全匹配

当用户名和性别都存在时, 用这两个条件进行查询匹配查询

2.1.2 动态 SQL

接口函数

/**

* 根据输入的学生信息进行条件检索

* 1. 当只输入用户名时, 使用用户名进行模糊检索;

* 2. 当只输入邮箱时, 使用性别进行完全匹配

* 3. 当用户名和性别都存在时, 用这两个条件进行查询匹配的用

*@paramstudent

*@return

*/

List selectByStudentSelective(Student student);

对应的动态 SQL

select

fromstudent

where1=1

andnamelikeconcat('%', #{name}, '%')

andsex=#{sex}

在此 SQL 语句中, where 1=1 是多条件拼接时的小技巧, 后面的条件查询就可以都用 and 了。

同时, 我们添加了 if 标签来处理动态 SQL

and name like concat('%',#{name}, '%')

and sex=#{sex}

此 if 标签的 test 属性值是一个符合 OGNL 的表达式, 表达式可以是 true 或 false。 如果表达式返回的是数值, 则0为 false, 非 0 为 true;

2.1.3 测试

@Test

publicvoidselectByStudent()

{

SqlSession sqlSession =null;

sqlSession = sqlSessionFactory.openSession();

StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);

Student search =newStudent();

search.setName("明");

System.out.println("只有名字时的查询");

List studentsByName = studentMapper.selectByStudentSelective(search);

for(inti =0; i < studentsByName.size(); i++) {

System.out.println(ToStringBuilder.reflectionToString(studentsByName.get(i), ToStringStyle.MULTI_LINE_STYLE));

}

search.setName(null);

search.setSex((byte)1);

System.out.println("只有性别时的查询");

List studentsBySex = studentMapper.selectByStudentSelective(search);

for(inti =0; i < studentsBySex.size(); i++) {

System.out.println(ToStringBuilder.reflectionToString(studentsBySex.get(i), ToStringStyle.MULTI_LINE_STYLE));

}

System.out.println("姓名和性别同时存在的查询");

search.setName("明");

List studentsByNameAndSex = studentMapper.selectByStudentSelective(search);

for(inti =0; i < studentsByNameAndSex.size(); i++) {

System.out.println(ToStringBuilder.reflectionToString(studentsByNameAndSex.get(i), ToStringStyle.MULTI_LINE_STYLE));

}

sqlSession.commit();

sqlSession.close();

}

只有名字时的查询, 发送的语句和结果

查询的条件只发送了

where1=1andname likeconcat('%', ?,'%')

只有性别时的查询, 发送的语句和结果

查询的条件只发送了

where1=1andsex=?

姓名和性别同时存在的查询, 发送的语句和结果

查询条件

where1=1andname likeconcat('%', ?,'%')andsex=?

2.2 在 UPDATE 更新列中使用 if 标签

有时候我们不希望更新所有的字段, 只更新有变化的字段。

2.2.1 更新条件

只更新有变化的字段, 空值不更新。

2.2.1 动态 SQL

接口方法

/**

* 更新非空属性

*/

intupdateByPrimaryKeySelective(Student record);

对应的 SQL

update student

`name` =#{name,jdbcType=VARCHAR},

phone =#{phone,jdbcType=VARCHAR},

email =#{email,jdbcType=VARCHAR},

sex =#{sex,jdbcType=TINYINT},

locked =#{locked,jdbcType=TINYINT},

gmt_created =#{gmtCreated,jdbcType=TIMESTAMP},

gmt_modified =#{gmtModified,jdbcType=TIMESTAMP},

wherestudent_id =#{studentId,jdbcType=INTEGER}

2.2.3 测试

@Test

publicvoidupdateByStudentSelective()

{

SqlSession sqlSession =null;

sqlSession = sqlSessionFactory.openSession();

StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);

Student student =newStudent();

student.setStudentId(1);

student.setName("明明");

student.setPhone("13838438888");

System.out.println(studentMapper.updateByPrimaryKeySelective(student));

sqlSession.commit();

sqlSession.close();

}

结果如下

2.3 在 INSERT 动态插入中使用 if 标签

我们插入数据库中的一条记录, 不是每一个字段都有值的, 而是动态变化的。 在这时候使用 if 标签, 可帮我们解决这个问题。

2.3.1 插入条件

只有非空属性才插入。

2.3.2 动态SQL

接口方法

/**

* 非空字段才进行插入

*/

intinsertSelective(Student record);

对应的SQL

insert into student

student_id,

`name`,

phone,

email,

sex,

locked,

gmt_created,

gmt_modified,

#{studentId,jdbcType=INTEGER},

#{name,jdbcType=VARCHAR},

#{phone,jdbcType=VARCHAR},

#{email,jdbcType=VARCHAR},

#{sex,jdbcType=TINYINT},

#{locked,jdbcType=TINYINT},

#{gmtCreated,jdbcType=TIMESTAMP},

#{gmtModified,jdbcType=TIMESTAMP},

这个 SQL 大家应该很熟悉, 毕竟是自动生成的。

2.3.3 测试

@Test

publicvoidinsertByStudentSelective()

{

SqlSession sqlSession =null;

sqlSession = sqlSessionFactory.openSession();

StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);

Student student =newStudent();

student.setName("小飞机");

student.setPhone("13838438899");

student.setEmail("xiaofeiji@qq.com");

student.setLocked((byte)0);

System.out.println(studentMapper.insertSelective(student));

sqlSession.commit();

sqlSession.close();

}

对应的结果

SQL 中, 只有非空的字段才进行了插入。

3 choose 标签

choose when otherwise 标签可以帮我们实现 if else 的逻辑。

一个 choose 标签至少有一个 when, 最多一个otherwise

下面是一个查询的例子。

3.1 查询条件

假设 name 具有唯一性, 查询一个学生

当 studen_id 有值时, 使用 studen_id 进行查询;

当 studen_id 没有值时, 使用 name 进行查询;

否则返回空

3.2 动态SQL

接口方法

/**

* - 当 studen_id 有值时, 使用 studen_id 进行查询;

* - 当 studen_id 没有值时, 使用 name 进行查询;

* - 否则返回空

*/

StudentselectByIdOrName(Student record);

对应的SQL

select

fromstudent

where1=1

andstudent_id=#{studentId}

andname=#{name}

and1=2

3.3 测试

@Test

publicvoidselectByIdOrName()

{

SqlSession sqlSession =null;

sqlSession = sqlSessionFactory.openSession();

StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);

Student student =newStudent();

student.setName("小飞机");

student.setStudentId(1);

Student studentById = studentMapper.selectByIdOrName(student);

System.out.println("有 ID 则根据 ID 获取");

System.out.println(ToStringBuilder.reflectionToString(studentById, ToStringStyle.MULTI_LINE_STYLE));

student.setStudentId(null);

Student studentByName = studentMapper.selectByIdOrName(student);

System.out.println("没有 ID 则根据 name 获取");

System.out.println(ToStringBuilder.reflectionToString(studentByName, ToStringStyle.MULTI_LINE_STYLE));

student.setName(null);

Student studentNull = studentMapper.selectByIdOrName(student);

System.out.println("没有 ID 和 name, 返回 null");

Assert.assertNull(studentNull);

sqlSession.commit();

sqlSession.close();

}

有 ID 则根据 ID 获取, 结果

没有 ID 则根据 name 获取

没有 ID 和 name, 返回 null

4 trim(set、where)

这三个其实解决的是类似的问题。 如我们在写前面的[在 WHERE 条件中使用 if 标签] SQL 的时候, where 1=1 这个条件我们是不希望存在的。

4.1 where

4.1.1 查询条件

根据输入的学生信息进行条件检索。

当只输入用户名时, 使用用户名进行模糊检索;

当只输入性别时, 使用性别进行完全匹配

当用户名和性别都存在时, 用这两个条件进行查询匹配查询

不使用 where 1=1。

4.1.2 动态 SQL

很显然, 我们要解决这几个问题

当条件都不满足时: 此时 SQL 中应该要不能有 where , 否则导致出错

当 if 有条件满足时: SQL 中需要有 where, 且第一个成立的 if 标签下的 and | or 等要去掉

这时候, 我们可以使用 where 标签。

接口方法

/**

* 根据输入的学生信息进行条件检索

* 1. 当只输入用户名时, 使用用户名进行模糊检索;

* 2. 当只输入邮箱时, 使用性别进行完全匹配

* 3. 当用户名和性别都存在时, 用这两个条件进行查询匹配的用

*/

ListselectByStudentSelectiveWhereTag(Student student);

对应的 SQL

select

fromstudent

andnamelikeconcat('%', #{name}, '%')

andsex=#{sex}

4.1.3 测试

@Test

publicvoidselectByStudentWhereTag()

{

SqlSession sqlSession =null;

sqlSession = sqlSessionFactory.openSession();

StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);

Student search =newStudent();

search.setName("明");

System.out.println("只有名字时的查询");

List studentsByName = studentMapper.selectByStudentSelectiveWhereTag(search);

for(inti =0; i < studentsByName.size(); i++) {

System.out.println(ToStringBuilder.reflectionToString(studentsByName.get(i), ToStringStyle.MULTI_LINE_STYLE));

}

search.setSex((byte)1);

System.out.println("姓名和性别同时存在的查询");

List studentsBySex = studentMapper.selectByStudentSelectiveWhereTag(search);

for(inti =0; i < studentsBySex.size(); i++) {

System.out.println(ToStringBuilder.reflectionToString(studentsBySex.get(i), ToStringStyle.MULTI_LINE_STYLE));

}

System.out.println("姓名和性别都不存在时查询");

search.setName(null);

search.setSex(null);

List studentsByNameAndSex = studentMapper.selectByStudentSelectiveWhereTag(search);

for(inti =0; i < studentsByNameAndSex.size(); i++) {

System.out.println(ToStringBuilder.reflectionToString(studentsByNameAndSex.get(i), ToStringStyle.MULTI_LINE_STYLE));

}

sqlSession.commit();

sqlSession.close();

}

只有名字时的查询, 有 where

姓名和性别同时存在的查询, 有 where

姓名和性别都不存在时查询, 此时, where 不会再出现了。

4.2 set

set 标签也类似, 在 [2.2 在 UPDATE 更新列中使用 if 标签] 中, 如果我们的方法 updateByPrimaryKeySelective 没有使用

4.3 trim

set 和 where 其实都是 trim 标签的一种类型, 该两种功能都可以使用 trim 标签进行实现。

4.3.1 trim 来表示 where

如以上的 where 标签, 我们也可以写成

表示当 trim 中含有内容时, 添加 where, 且第一个为 and 或 or 时, 会将其去掉。 而如果没有内容, 则不添加 where。

4.3.2 trim 来表示 set

相应的, set 标签可以如下表示

表示当 trim 中含有内容时, 添加 set, 且最后的内容为 , 时, 会将其去掉。 而没有内容, 不添加 set

4.3.3 trim 的几个属性

prefix: 当 trim 元素包含有内容时, 增加 prefix 所指定的前缀

prefixOverrides: 当 trim 元素包含有内容时, 去除 prefixOverrides 指定的 前缀

suffix: 当 trim 元素包含有内容时, 增加 suffix 所指定的后缀

suffixOverrides: 当 trim 元素包含有内容时, 去除 suffixOverrides 指定的后缀

5 foreach 标签

foreach 标签可以对数组, Map 或实现 Iterable 接口。

foreach 中有以下几个属性

collection: 必填, 集合/数组/Map的名称.

item: 变量名。 即从迭代的对象中取出的每一个值

index: 索引的属性名。 当迭代的对象为 Map 时, 该值为 Map 中的 Key.

open: 循环开头的字符串

close: 循环结束的字符串

separator: 每次循环的分隔符

其他的比较好理解, collection 中的值应该怎么设定呢?

跟接口方法中的参数相关。

1. 只有一个数组参数或集合参数

默认情况: 集合collection=list, 数组是collection=array

推荐: 使用 @Param 来指定参数的名称, 如我们在参数前@Param("ids"), 则就填写 collection=ids

2. 多参数

多参数请使用 @Param 来指定, 否则SQL中会很不方便

3. 参数是Map

指定为 Map 中的对应的 Key 即可。 其实上面的 @Param 最后也是转化为 Map 的。

4. 参数是对象

使用属性.属性即可。

5.1 在 where 中使用 foreach

在 where条件中使用, 如按id集合查询, 按id集合删除等。

5.1.1 查询条件

我们希望查询用户 id 集合中的所有用户信息。

5.1.2 动态 SQL

函数接口

/**

* 获取 id 集合中的用户信息

*@paramids

*@return

*/

List selectByStudentIdList(List ids);

对应 SQL

select

fromstudent

wherestudent_idin

#{id}

5.1.3 测试

@Test

publicvoidselectByStudentIdList()

{

SqlSession sqlSession =null;

sqlSession = sqlSessionFactory.openSession();

StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);

List ids =newLinkedList<>();

ids.add(1);

ids.add(3);

List students = studentMapper.selectByStudentIdList(ids);

for(inti =0; i < students.size(); i++) {

System.out.println(ToStringBuilder.reflectionToString(students.get(i), ToStringStyle.MULTI_LINE_STYLE));

}

sqlSession.commit();

sqlSession.close();

}

结果

5.2 foreach 实现批量插入

可以通过foreach来实现批量插入。

5.2.1 动态SQL

接口方法

/**

* 批量插入学生

*/

intinsertList(List<Student> students);

对应的SQL

insertintostudent(name, phone, email, sex, locked)

values

="list"item="student"separator=",">

(

#{student.name}, #{student.phone},#{student.email},

#{student.sex},#{student.locked}

)

5.2.2 测试

@Test

publicvoidinsertList()

{

SqlSession sqlSession =null;

sqlSession = sqlSessionFactory.openSession();

StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);

List students =newLinkedList<>();

Student stu1 =newStudent();

stu1.setName("批量01");

stu1.setPhone("13888888881");

stu1.setLocked((byte)0);

stu1.setEmail("13888888881@138.com");

stu1.setSex((byte)1);

students.add(stu1);

Student stu2 =newStudent();

stu2.setName("批量02");

stu2.setPhone("13888888882");

stu2.setLocked((byte)0);

stu2.setEmail("13888888882@138.com");

stu2.setSex((byte)0);

students.add(stu2);

System.out.println(studentMapper.insertList(students));

sqlSession.commit();

sqlSession.close();

}

结果

6 bind 标签

bind 标签是通过 OGNL 表达式去定义一个上下文的变量, 这样方便我们使用。

如在 selectByStudentSelective 方法中, 有如下

andname likeconcat('%', #{name},'%')

在 MySQL 中, 该函数支持多参数, 但在 Oracle 中只支持两个参数。 那么我们可以使用 bind 来让该 SQL 达到支持两个数据库的作用

and name like#{nameLike}

更改后的查询结果如下

7 代码

使用示例:https://github.com/homejim/mybatis-examples

扩展阅读

不改表结构如何动态扩展字段

一千行 MySQL 学习笔记

MyBatis拦截器原理探究

Spring4+Spring MVC+MyBatis整合思路

MySQL之SQL优化实战记录

来源:https://www.cnblogs.com/homejim/p/9909657.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容

  • 一. Java基础部分.................................................
    wy_sure阅读 3,809评论 0 11
  • 用来循环容器的标签forEach,查看例子 foreach元素的属性主要有item,index,collectio...
    Java面试指南阅读 428评论 0 0
  • 前言 主题是Mybatis一级和二级缓存的应用及源码分析。希望在本场chat结束后,能够帮助读者朋友明白以下三点。...
    余平的余_余平的平阅读 1,325评论 0 12
  • .我们家的圆圆的过生曰还领了结婚证,.姨妈在很远的.他乡,从表姐的微信中看到,我们家圆圆长大,成熟了,而且找到自已...
    尘缘1227阅读 702评论 0 3
  • ——如果真有来世,我们定会再见! “记得常来玩 ”,在一声声的叨念中,我们离开了你家。仍记得小时候,经常喜欢到...
    北冥湫阅读 268评论 0 1