在我们日常编码中,mapper 文件无疑使我们最重要的工作,因为他承载着我们所有业务中的增删改查所涉及到的 sql 语句编写,那么我们今天就来系统的学习下常用的sql 语句在mybatis 中应该如何编写。
XML映射文件
在开始编写映射文件之前我我们先来认识下几个常用的标签。
- cache ——对给定命名空间的缓存配置
- cache-ref ——对其他命名空间缓存配置的引用
- resultMap ——结果集映射(功能十分强大)
- sql —— 声明sql片段,可以被其他语句引用
- insert —— 插入语句
- update ——更新语句
- delete ——删除语句
- select ——查询语句
insert | delete | update
由于在 mybatis 中增删改的操作十分相似,这里就统一说明。在开始之前,先介绍下insert | delete | update 标签中的属性
属性 | 描述 |
---|---|
id | 命名空间中的唯一标识符,我们用 mapper 接口的方法名与该id值对应 |
parameterType | 参数类型,为类的全限定名或别名,此属性为可选配置 |
flushCache | 设置为 true 时,在执行sql时或刷新本地缓存和耳机缓存,默认值为 true |
timeout | 超时时间,默认为未设置 |
statementType | STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 |
useGeneratedKeys | 通过jdbc获取数据库自增主键,默认值为 false |
keyProperty | mybatis 会将获取到的自增主键的值设置的该属性的值 |
insert
- 简单插入
<insert id="saveUser" parameterType="org.bmth.mybatis.entity.User">
INSERT INTO `users` (`username`, `password`, `nickname`, `avatar`,`age`, `sex`, `telephone`, `enable`)
VALUES (#{username}, #{password}, #{nickname}, #{avatar}, #{age}, #{sex},
#{telephone}, #{enable});
</insert>
- 插入并返回自增主键
<insert id="saveUserAndTakeId" useGeneratedKeys="true" keyProperty="id" parameterType="org.bmth.mybatis.entity.User">
INSERT INTO `users` (`username`, `password`, `nickname`, `avatar`, `age`, `sex`, `telephone`, `enable`)
VALUES (#{username}, #{password}, #{nickname}, #{avatar}, #{age}, #{sex},
#{telephone}, #{enable});
</insert>
注: 这里的自增主键的值将会赋值给 User
实体的 id 属性值。在代码中可以通过 User
的实例获取。
- 批量插入(这里通过
<foreach>
标签实现)
<insert id="batchSave">
INSERT INTO `users` (`username`, `password`, `nickname`, `avatar`, `age`, `sex`, `telephone`, `enable`) VALUES
<foreach item="item" collection="users" separator=",">
(#{item.username}, #{item.password}, #{item.nickname}, #{item.avatar}, #{item.age}, #{item.sex}, #{item.telephone}, #{item.enable});
</foreach>
</insert>
注: 这里的 collection="users"
的 users 属性 来自于 mapper 接口层 ,通过 @Param
标签声名。
void batchSave(@Param("users") List<User> users);
delete
<delete id="deleteById" parameterType="java.lang.Integer">
DELETE FROM `users` WHERE `id`= #{id};
</delete>
update
<update id="updateById" parameterType="org.bmth.mybatis.entity.User">
UPDATE `users` SET `username`=#{username}, `password`=#{password}, `nickname`=#{nickname},
`avatar`=#{avatar}, `age`=#{age}, `telephone`=#{telephone}, `enable`=#{enable} WHERE `id`=#{id};
</update>
sql
这个元素可以被用来定义可重用的 SQL 代码段,这些 SQL 代码可以被包含在其他语句中。
<sql id="userColumns">
`username`, `password`, `nickname`, `avatar`, `age`, `sex`, `telephone`,`address`, `enable`
</sql>
这个 SQL 片段可以通过 <include>
标签被其他 SQL 语句引用
<select id="findAll" resultType="org.bmth.mybatis.entity.User">
SELECT
<include refid="userColumns"/>
FROM `users`;
</select>
当然,在<sql>
标签中我们也可以提前声明占位符:
<sql id="userColumns">
${alias}.`username`, ${alias}.`password`, ${alias}.`nickname`,
${alias}.`avatar`,${alias}.`age`,${alias}.`sex`,
${alias}.`telephone`,${alias}.`address`,${alias}.`enable`
</sql>
然后通过 <include>
标签中进行占位符替换:
<select id="findAll" resultType="org.bmth.mybatis.entity.User">
SELECT
<include refid="userColumns">
<property name="alias" value="u"/>
</include>
FROM `users` u;
</select>
select
查询绝对在大多数业务场景中都占据这主导地位,而mybatis最强大之处就在于sql语句的灵活性,以及强大的结果集映射。由于结果映射比较复杂,我们会在后面单独来讲。这里先介绍下 <select>
标签中的属性。
属性 | 描述 |
---|---|
id | 唯一标识,我们用 mapper 接口的方法名与该id值对应 |
parameterType | 参数类型,为类的全限定名或别名,此属性为可选配置 |
resultType | 返回类型,为类的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身 |
resultMap | 结果集映射,对 <resultMap> 标签命名的引用 |
flushCache | 设置为 true 时,在执行sql时或刷新本地缓存和耳机缓存,默认值为 false,这里需要注意的是,在 insert | update | delete 标签中默认值为 true,在 select 标签中,默认值为 false |
useCache | 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。 |
timeout | 超时时间,默认为未设置 |
fetchSize | 尝试让每次批量返回的结果行数和这个设置值相等。 默认值为未设置 |
statementType | STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 |
resultOrdered | 针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。 这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false 。 |
resultSets | 这个设置仅对多结果集的情况适用。它将列出语句执行后返回的结果集并给每个结果集一个名称,名称是逗号分隔的。 |
看见这么多可设置的属性,是不是感觉我太难了,不用担心,简单的查询其实很简单,比如,我们要根据id查询一个用户:
<select id="findOneById" parameterType="int" resultType="org.bmth.mybatis.entity.User">
SELECT
<include refid="userColumns" />
FROM `users` WHERE `id` = #{id};
</select>
这个语句接受一个 int 类型的参数,返回一个 user 类型的结果,这里可能有同学会问,为什么不是 parameterType="java.lang.Integer"
, 上面提到过,这里接收的是一个类的全限定名或者别名,很显然,这里就是 mybatis 为我们预先提供好的一些别名,这里在后面源码分析篇中会很清晰的看到 mybatis为我们提供了很多的别名,来方便我们开发的中使用。
参数传递
我们已经见到过了参数传递的代码,其实并不复杂,而且在我们平时的工作中,也很少用到太过于复杂的参数传递行为。比如在上面的示例中,就是我们常用的一种最简单的参数传递方式:
<select id="findOneById" parameterType="int" resultType="org.bmth.mybatis.entity.User">
SELECT
<include refid="userColumns" />
FROM `users` WHERE `id` = #{id};
</select>
这里就是讲一个 int 类型的 id 作为参数传递,当然这里我们还可以传递一个对象类型,比如我们的用户插入操作,就是将一个对象类型作为参数传递,而我们只需要在#{}
中添加我们想要获取的对象属性即可。
<insert id="saveUser" parameterType="org.bmth.mybatis.entity.User">
INSERT INTO `users` (`username`, `password`, `nickname`, `avatar`,`age`, `sex`, `telephone`, `enable`)
VALUES (#{username}, #{password}, #{nickname}, #{avatar}, #{age}, #{sex},
#{telephone}, #{enable});
</insert>
当然,有些时候单一的参数类型无法满足我们的日常使用,比如我们想要查询 username = ‘小明’ 并且 age = 18 的数据,显然这里我们无法将两个不同类型的参数利用 parameterType 声明,因此,我们需要这样利用 @Param
注解来实现这个功能。
//mapper 接口
List<User> findAllByUsernameAndAge(@Param("username") String username,
@Param("age") int age);
<!--映射文件-->
<select id="findAllByUsernameAndAge" resultType="org.bmth.mybatis.entity.User">
SELECT
<include refid="userColumns" />
FROM `users` WHERE `username` = #{username,jdbcType=VARCHAR} AND `age` = #{age};
</select>
可以看到 @Param
注解的值,和 #{}
是绑定的,在 mybatis 中,执行 SQL 语句时,会将 #{} 替换成 ?,这样做可以有效的防止SQL注入,不过有时你就是想直接在 SQL 语句中插入一个不转义的字符串。 比如,像 ORDER BY,你可以使用 ${} 来实现,还是上面的例子,我们做一下修改,可以实现了。
//mapper 接口
List<User> findAllByUsernameAndAge(@Param("username") String username,
@Param("age") int age,
@Param("columnName") String columnName);
<!--映射文件-->
<select id="findAllByUsernameAndAge" resultType="org.bmth.mybatis.entity.User">
SELECT
<include refid="userColumns" />
FROM `users` WHERE `username` = #{username,jdbcType=VARCHAR} AND `age` = #{age}
ORDER BY ${columnName} ;
</select>
总结:在mybatis中, ${}
会被直接替换,而 #{}
会被使用 ?
预处理。
结果映射
可以说,ResultMap 是 MyBatis 中最强大的元素,而且对于简单的映射关系,根本不需要显示的声明,比如我们前面已经见到了简单的映射示例:
简单映射
<!--映射文件-->
<select id="findAllByUsernameAndAge" resultType="org.bmth.mybatis.entity.User">
SELECT
<include refid="userColumns" />
FROM `users` WHERE `username` = #{username,jdbcType=VARCHAR} AND `age` = #{age}
ORDER BY ${columnName} ;
</select>
这里就是直接将我们所查询的列,映射到了 User
类,那么如果所查询的列和我们所要映射的属性不一致怎么办?其实很简单,我们只需要用 SQL 的 AS 作为别名来匹配即可,比如:
<select id="findAllByUsernameAndAge" resultType="org.bmth.mybatis.entity.User">
SELECT
`id` AS id,
`username` AS username,
`password` AS password
FROM `users` WHERE `username` = #{username,jdbcType=VARCHAR} AND `age` = #{age}
ORDER BY ${columnName} ;
</select>
当然,这里我们也可以显示的声明 ResultMap
来解决列名不匹配的问题:
<!--resultMap-->
<resultMap id="userResultMap" type="org.bmth.mybatis.entity.User">
<id property="id" column="id"/>
<result property="username" column="username" />
<result property="password" column="password" />
<result property="age" column="age" />
</resultMap>
<select id="findAllByUsernameAndAge" resultMap="userResultMap">
SELECT
`id`,
`username`,
`password`,
`age`
FROM `users` WHERE `username` = #{username,jdbcType=VARCHAR} AND `age` = #{age}
ORDER BY ${columnName} ;
</select>
复杂映射
当然,有很多时候我们需要构造出一个比较复杂的对象,此时 resultMap
的强大之处就得到了很好的体现,假如现在有三张表:用户表 ,收获地址表,以及地址标签表
现在我们需要根据用户来查询到他所有的收货地址,及地址的标签,那么需要构造一个对象如下:
@Data
public class Address {
private Integer id;
private String province;
private String city;
private String region;
private String detailAddress;
private String labelName;
private String phoneNumber;
}
@Data
public class User{
private Integer id;
private String username;
private String password;
private String avatar;
private String nickname;
private Integer sex;
private Integer age;
private String telephone;
private Boolean enable;
private Date createTime;
private List<Address> addressList;
}
现在我们就利用 resultMap 来映射 User
实体:
<resultMap id="userDetailResultMap" type="org.bmth.mybatis.entity.User">
<id property="id" column="id"/>
<result property="username" column="username" />
<collection property="addressList" ofType="org.bmth.mybatis.entity.Address">
<id column="address_id" property="id" />
<result column="province" property="province"/>
<result column="city" property="city"/>
<result column="region" property="region"/>
<result column="detail_address" property="detailAddress"/>
<result column="region" property="region"/>
<result column="name" property="labelName"/>
<result column="phone_number" property="phoneNumber"/>
</collection>
</resultMap>
<select id="getUserDetail" parameterType="int" resultMap="userDetailResultMap">
SELECT
u.id,
u.username,
ra.address_id,
ra.phone_number,
ra.province,
ra.city,
ra.region,
ra.detail_address,
al.name
FROM
users u
INNER JOIN
receive_address ra ON u.id = ra.user_id
LEFT JOIN
address_label al ON ra.label_id = al.label_id
WHERE u.id = #{userId};
</select>
这样就完成了一个比较复杂的映射,resultMap
的强大之处不仅于此,这里就不一一列举,还是一起来看下resultMap
的映射结构。
嵌套查询
还是上面的需求,我们还有另一种方式来实现,就是在 resultMap
中做嵌套查询:
<resultMap id="userDetailResultMap2" type="org.bmth.mybatis.entity.User">
<id property="id" column="id"/>
<result property="username" column="username" />
<collection property="addressList" column="id" ofType="org.bmth.mybatis.entity.Address"
select="listAddressByUserId"/>
</resultMap>
<select id="getUserDetail2" parameterType="int" resultMap="userDetailResultMap2">
SELECT
u.id,
u.username
FROM
users u
WHERE u.id = #{userId};
</select>
<select id="listAddressByUserId" resultType="org.bmth.mybatis.entity.Address">
SELECT
ra.address_id,
ra.phone_number,
ra.province,
ra.city,
ra.region,
ra.detail_address,
al.name
FROM
receive_address ra
LEFT JOIN
address_label al ON ra.label_id = al.label_id
WHERE ra.user_id = #{userId};
</select>
resultMap映射
-
constructor
- 用于在实例化类时,注入结果到构造方法中
idArg
- ID 参数;标记出作为 ID 的结果可以帮助提高整体性能arg
- 将被注入到构造方法的一个普通结果
-
id
– 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能 -
result
– 注入到字段或 JavaBean 属性的普通结果 -
association
– 一个复杂类型的关联;许多结果将包装成这种类型
- 嵌套结果映射 – 关联本身可以是一个
resultMap
元素,或者从别处引用一个
- 嵌套结果映射 – 关联本身可以是一个
-
collection
– 一个复杂类型的集合
- 嵌套结果映射 – 集合本身可以是一个
resultMap
元素,或者从别处引用一个
- 嵌套结果映射 – 集合本身可以是一个
-
discriminator
– 使用结果值来决定使用哪个
resultMap
-
case
– 基于某些值的结果映射
-
嵌套结果映射 –
case
本身可以是一个resultMap
元素,因此可以具有相同的结构和元素,或者从别处引用一个
resultMap
属性列表
-
-
属性 | 描述 |
---|---|
id | 当前命名空间中的一个唯一标识,用于标识一个结果映射。 |
type | 类的完全限定名, 或者一个类型别名(关于内置的类型别名,可以参考上面的表格)。 |
autoMapping | 如果设置这个属性,MyBatis将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。默认值:未设置。 |
id & result
<id property="id" column="id"/>
<result property="username" column="username" />
id 元素表示的结果将是对象的标识属性,这会在比较对象实例时用到。 这样可以提高整体的性能,尤其是进行缓存和嵌套结果映射(也就是连接映射)的时候。
两个元素都有一些属性:
属性 | 描述 |
---|---|
property | 映射到列结果的字段或属性。如果用来匹配的 JavaBean 存在给定名字的属性,那么它将会被使用 |
column | 数据库中的列名,或者是列的别名。 |
javaType | 一个 Java 类的完全限定名,或一个类型别名 |
jdbcType | JDBC 类型 |
typeHandler | 我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的完全限定名,或者是类型别名。 |
association
属性 | 描述 |
---|---|
property | 映射到列结果的字段或属性 |
javaType | 一个 Java 类的完全限定名,或一个类型别名 |
jdbcType | JDBC 类型 |
typeHandler | 类型处理器 |
collection
<collection property="addressList" ofType="org.bmth.mybatis.entity.Address">
<id column="address_id" property="id" />
<result column="province" property="province"/>
<result column="city" property="city"/>
<result column="region" property="region"/>
<result column="detail_address" property="detailAddress"/>
<result column="region" property="region"/>
<result column="name" property="labelName"/>
<result column="phone_number" property="phoneNumber"/>
</collection>
和 association
差不多,不在赘述。
discriminator
一个数据库查询可能会返回多个不同的结果集(但总体上还是有一定的联系的)。 鉴别器(discriminator)元素就是被设计来应对这种情况的,另外也能处理其它情况,例如类的继承层次结构。 鉴别器的概念很好理解——它很像 Java 语言中的 switch 语句。