MyBatis 基础表达式语法

语句映射

select

select 拥有很多属性,以下是常用属性:

  • id:命名空间中唯一标识符,可用于被引用。
  • parameterType:将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认为未设置(unset)。
  • resultType:期望从这条语句中返回结果的类全限定名或别名。 如果返回集合,那么应设置为集合中元素的类型。 resultType 和 resultMap 之间只能选用一个。
  • flushCache:如果开启,那么语句被调用时会清空本地缓存和二级缓存。默认为 false。
  • useCache:如果开启,那么语句调用后返回的结果会缓存存到二级缓存,默认为 true。
  • timeout:在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。

查询某个人的信息:

1
2
3
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM person WHERE id = #{id}
</select>

insert, update, delete

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<insert id="insertAuthor">
insert into Author (id,username,password,email,bio)
values (#{id},#{username},#{password},#{email},#{bio})
</insert>

<update id="updateAuthor">
update Author set
username = #{username},
password = #{password},
email = #{email},
bio = #{bio}
where id = #{id}
</update>

<delete id="deleteAuthor">
delete from Author where id = #{id}
</delete>

插入后获得生成的主键

如果数据库支持自动生成主键的字段,那么可以设置 useGeneratedKeys="true",然后再把 keyProperty 设置为目标属性。
例如,如果上面的 Author 表已经在 id 列上使用了自动生成,那么语句可以修改为:

1
2
3
4
5
<insert id="insertAuthor" useGeneratedKeys="true"
keyProperty="id">
insert into Author (username,password,email,bio)
values (#{username},#{password},#{email},#{bio})
</insert>

如果数据库支持多行插入,也可以返回自动生成的主键:

1
2
3
4
5
6
7
<insert id="insertAuthor" useGeneratedKeys="true"
keyProperty="id">
insert into Author (username, password, email, bio) values
<foreach item="item" collection="list" separator=",">
(#{item.username}, #{item.password}, #{item.email}, #{item.bio})
</foreach>
</insert>

动态SQL

MyBatis 3 基于 OGNL 的表达式编写动态 SQL。

if

if代表根据条件拼接语句。

例如,根据标题和作者名查找活跃状态的博客,当test为真就会拼接对应if元素里的语句:

1
2
3
4
5
6
7
8
9
10
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = 'ACTIVE'
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>

choose, when, otherwise

choose元素代表从多个条件中选择一个使用。

例如,根据标题查找活跃状态的博客,如果标题为空那么根据作者名查找,如果都为空,那么查找精选的博客:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = 'ACTIVE'
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>

trim、where、set

where元素只会在子元素返回任何内容的情况下才插入 WHERE 子句。若子句以ANDOR开头,where元素也会去除它。

set元素可以用于动态包含需要更新的列,并删掉额外的逗号。

例如,更新作者信息:

1
2
3
4
5
6
7
8
9
10
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>

trim元素用来“修剪”拼接的语句。它有如下属性:

  • prefix:加上前缀
  • suffix:加上后缀
  • preffixOverrides:去除指定的前缀
  • suffixOverrides:去除指定的后缀

例如,和where元素等价的自定义trim元素为:

1
2
3
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>

例如,和set元素等价的自定义trim元素为:

1
2
3
<trim prefix="SET" suffixOverrides=",">
...
</trim>

例如,向班级信息表中插入非空数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<insert id="insert" parameterType="com.morning.all.entity.ClassInfo">
INSERT INTO tb_class
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="className != null">
class_name,
</if>
<if test="classAddress != null">
class_address,
</if>
</trim>
VALUES
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="className != null">
#{className, jdbcType=VARCHAR},
</if>
<if test="classAddress != null">
#{classAddress, jdbcType=VARCHAR},
</if>
</trim>
</insert>

foreach

foreach元素用于对集合进行遍历,常用于构建in条件语句或者批量操作语句。它有如下属性:

  • collection:迭代集合的名称,比如listarrayset,也可用@Param注解指定名称;
  • item:本次迭代获取的元素,若collectionmap,则代表键值对的值;
  • open:语句拼接的前缀;
  • close:语句拼接的后缀;
  • separator:每次迭代后给 sql 语句附加上的指定的字符,并且不会错误添加多余的分隔符;
  • index:本次迭代获取的元素的序号,若collectionmap,则代表键值对的键。

例如,遍历指定 ID 集合对应的发布文章:

1
2
3
4
5
6
7
8
9
10
<select id="selectPostIn" resultType="Post">
SELECT *
FROM post
<where>
<foreach item="item" index="index" collection="list"
open="id IN (" separator="," close=")" nullable="true">
#{item}
</foreach>
</where>
</select>

例如,批量插入用户信息:

1
2
3
4
5
6
7
<insert id="insertUsers">
INSERT INTO t_user(username,age,score)
VALUES
<foreach collection="users" item="user" separator=",">
(#{user.username}, #{user.age}, #{user.score})
</foreach>
</insert>

嵌套结果映射

联表查询

集合的嵌套结果映射(一对多)

一个博客可以对应多个文章:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<select id="selectBlog" resultMap="blogResult">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
P.id as post_id,
P.subject as post_subject,
P.body as post_body,
from Blog B
left outer join Post P on B.id = P.blog_id
where B.id = #{id}
</select>

<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<result property="body" column="post_body"/>
</collection>
</resultMap>

将上面的结果映射改成可重用的等价形式:

1
2
3
4
5
6
7
8
9
10
11
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/>
</resultMap>

<resultMap id="blogPostResult" type="Post">
<id property="id" column="id"/>
<result property="subject" column="subject"/>
<result property="body" column="body"/>
</resultMap>

关联的嵌套结果映射(多对一或一对一)

一个博客对应一个作者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<select id="selectBlog" resultMap="blogResult">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
A.id as author_id,
A.username as author_username,
A.password as author_password,
A.email as author_email,
A.bio as author_bio
from Blog B left outer join Author A on B.author_id = A.id
where B.id = #{id}
</select>

<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<association property="author" column="blog_author_id" javaType="Author" resultMap="authorResult"/>
</resultMap>

<resultMap id="authorResult" type="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
</resultMap>

分步查询

分步查询在大型数据集或大型数据表上表现不佳。这个问题被称为“N+1 查询问题”。 概括地讲,N+1 查询问题是这样子的:

  • 你执行了一个单独的 SQL 语句来获取结果的一个列表(就是“+1”)。
  • 对列表返回的每条记录,你执行一个 select 查询语句来为每条记录加载详细信息(就是“N”)。

这个问题会导致成百上千的 SQL 语句被执行。有时候,我们不希望产生这样的后果。

好消息是,MyBatis 能够对这样的查询进行延迟加载,因此可以将大量语句同时运行的开销分散开来。 然而,如果你加载记录列表之后立刻就遍历列表以获取嵌套的数据,就会触发所有的延迟加载查询,性能可能会变得很糟糕。

集合的嵌套结果映射(一对多)

一个商品可以对应多个商品详情,在商品实体类中有一个商品详情的集合字段,结果映射如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<resultMap id="rmGoods1" type="Goods">
<!-- 映射goods对象的主键到goods_id字段 -->
<id column="goods_id" property="goodsId"></id>
<!--
collection的含义是,在
select * from t_goods limit 0,1 得到结果后,对所有Goods对象遍历得到goods_id字段值,
并代入到goodsDetail命名空间的findByGoodsId的SQL中执行查询,
将得到的"商品详情"集合赋值给goodsDetails List对象.
-->
<collection property="goodsDetails" select="goodsDetail.selectByGoodsId" column="goods_id"/>
</resultMap>
<select id="selectOneToMany" resultMap="rmGoods1">
select * from t_goods limit 0,10
</select>

关联的嵌套结果映射(多对一或一对一)

商品详情对应一个商品,在商品详情实体类中有一个商品ID字段,结果映射如下:

1
2
3
4
5
6
7
8
<resultMap id="rmGoodsDetail" type="GoodsDetail">
<id column="gd_id" property="gdId"/>
<result column="goods_id" property="goodsId"/>
<association property="goods" select="goods.selectById" column="goods_id"></association>
</resultMap>
<select id="selectManyToOne" resultMap="rmGoodsDetail">
select * from t_goods_detail limit 0,20
</select>