MyBatis 映射文件

在 MyBatis 中,可以通过 XxxMapper.xml 映射文件,将 SQL 语句与数据操作方法相绑定。

一个最简单的映射文件样例:

1
2
3
4
5
6
7
8
9
10
<?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="接口全类名">
<子标签名 id="方法名">
SQL 语句
</子标签名>
</mapper>

其中:

  • mapper 标签

    • namespace 属性指定接口
  • 子标签

    • id 属性指定方法名
    • 子标签名指定方法的类型
    • 其它属性描述实现方法的参数、返回值等

一、namespace

在之前版本的 MyBatis 中,命名空间(Namespaces)的作用并不大,是可选的。 但现在,随着命名空间越发重要,你必须指定命名空间。

命名空间的作用有两个,一个是利用更长的全限定名来将不同的语句隔离开来,同时也实现了你上面见到的接口绑定。就算你觉得暂时用不到接口绑定,你也应该遵循这里的规定,以防哪天你改变了主意。 长远来看,只要将命名空间置于合适的 Java 包命名空间之中,你的代码会变得更加整洁,也有利于你更方便地使用 MyBatis。

命名解析:为了减少输入量,MyBatis 对所有具有名称的配置元素(包括语句,结果映射,缓存等)使用了如下的命名解析规则。

  • 全限定名(比如 “com.mypackage.MyMapper.selectAllThings)将被直接用于查找及使用。
  • 短名称(比如 “selectAllThings”)如果全局唯一也可以作为一个单独的引用。 如果不唯一,有两个或两个以上的相同名称(比如 “com.foo.selectAllThings” 和 “com.bar.selectAllThings”),那么使用时就会产生“短名称不唯一”的错误,这种情况下就必须使用全限定名。

namespace 的作用是:

  • 将不同的方法分隔开

    不同 Mapper 下的同名方法不会冲突

  • 实现接口绑定

    将 xml 文件与接口相互绑定,

    Mybatis 可以根据 namespace 寻找到对应的接口,根据 id 寻找到对应的方法,构造 mapper 实例

二、select

1. 说明

用于映射查询方法。

2. 示例

1
2
3
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
</select>
  • 映射名为 selectPerson 的方法
  • 接收一个 int 类型的参数
  • 返回一个 hashmap 类型的对象
  • 映射的 SQL 语句为 SELECT * FROM PERSON WHERE ID = #{id}

3. 属性

属性 描述
id 在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
resultType 期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。
resultMap 对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。
flushCache 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。
useCache 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
fetchSize 这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。 默认值为未设置(unset)(依赖驱动)。
statementType 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
resultSetType FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。
databaseId 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。
resultOrdered 这个设置仅针对嵌套结果 select 语句:如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false
resultSets 这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。

其中,

  • id:用于唯一标识语句,应该与所映射的方法名相同

  • 描述传入参数:

    • parameterType:传入参数的类名,可选

      MyBatis 能够通过类型处理器推当初参数类型

  • 描述返回结果:

    resultType 和 resultMap 二选一

    • resultType:返回结果的类名

      若返回的是一个集合,则此处应该填集合元素的类型,而非集合本身的类型

    • resultMap:引用 resultMap,通过它来描述返回结果

三、insert, update 和 delete

1. 说明

分别用于映射插入、更新和删除方法。

2. 示例

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>

3. 属性

属性 描述
id 在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
flushCache 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:(对 insert、update 和 delete 语句)true。
timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
statementType 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
useGeneratedKeys (仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。
keyProperty (仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,可以用逗号分隔多个属性名称。
keyColumn (仅适用于 insert 和 update)设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。
databaseId 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。

其中,

  • id:用于唯一标识语句,应该与所映射的方法名相同
  • 描述传入参数:
    • parameterType:传入参数的类名,可选

4. 递增字段

在实际应用中,往往需要给数据一个”无意义但不重复”的字段,用于区分每一条数据。

例如:

1
2
3
000001    zhang    23
000002 li 22
000003 wang 19

可以通过 useGeneratedKeys=”true” 取得由数据库内部自动生成的主键,再将 keyProperty 设为目标属性即可。

1
2
3
4
<insert id="方法名" useGeneratedKeys="true"
keyProperty="属性名">
···
</insert>

三、传入参数

1. parameterType

parameterType 用于指定传入参数的类型

只能传入一个参数

可选,因为 Mybatis 会自动识别

2. 传入一个参数

直接填写参数类型,或者不填也可以

3. 传入多个参数

可以通过以下方式传入多个参数:

  • 将参数写在对象中

  • 将参数写在 Map 中

  • 使用 @Param 注解参数

  • 顺序传值法:按顺序传入参数,通过 param[n]arg[n] 使用参数

    其中,n 表示第几个参数

有时虽然不加 @Param 注解,但参数仍然能够顺利传入。

这是由于本地 IDE 默认添加了 -parameters 参数,使得方法的参数名在编译时强制保留,从而能够顺利取到参数。

4. 使用参数

  • 当传入一个参数时,使用 #{任意命名} 使用参数

    1
    2
    3
    <select id="getUserById" resultType="Map">
    select * from user where id = #{随意}
    </select>
  • 当传入对象或 Map 时,使用 #{属性名/key 值} 使用参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    HashMap<String, Integer> map = new HashMap<>();
    map.put("username", "root");
    map.put("password", "root");

    ···

    <select id="getUserById" resultType="Map">
    select
    *
    from
    user
    where
    username = #{username}
    and password = #{password}
    </select>

5. 参数占位符

在 MyBatis 中,使用 #{}${} 作为参数占位符。它们将占据位置并等待数值插入。

其中,

  • ${} 拿到数值后直接进行字符串拼接
  • #{} 更加安全,可以防止 SQL 注入

四、返回结果

1. 取出返回结果

对于 select 来说,执行完之后应该取出结果。在 MyBatis 中,需要通过 resultType 或 resultMap 描述返回结果。

实际应用中,以下情形可以很方便地取出:

  • 返回结果为单个字段,设置 resultType 为对应基本类型,直接取出即可

    1
    2
    3
    4
    5
    6
    7
    String getUserNameById(int id);

    ···

    <select id="getUserNameById" resultType="string">
    select username from user where id = #{id}
    </select>
  • 返回结果由多个字段组成(或者是多个由多个字段组成的信息组成的集合),且字段与实体类中的属性一一对应,设置 resultType 为 实体类名,直接取出即可

    1
    2
    3
    4
    5
    6
    7
    User getUserById(int id);

    ···

    <select id="getUserById" resultType="pojo.User">
    select * from user where id = #{id}
    </select>

但如果返回结果中的字段与实体类中的属性不对应,便需要寻找解决方案。

2. 取出多个字段

假设实体类为:

1
2
3
4
5
public class User {
private int id;
private String name;
private String pwd;
}

数据库为:

字段名与属性名无法一一对应,此时有几种解决方案:

(1) 在 select 中设置列别名

1
2
3
4
5
6
7
8
<select id="getUserById" resultType="pojo.User">
select
id,
username as "name",
password as "pwd"
from user
where id = #{id}
</select>

(2) 使用 Map

1
2
3
4
5
6
7
8
<select id="getUserById" resultType="Map">
select * from user where id = #{id}
</select>

···

Map<String, Object> map = mapper.getUserById(1);
User user = new User(map.get("id"), map.get("username")), map.get("password")))

将返回结果赋给 Map,返回结果会以字段名为 key,值为 value 放入 Map 之中。再自行取出 Map 中的值,放入实体类对象中。

(3) 使用 resultMap

MyBatis resultMap

1
2
3
4
5
6
7
8
9
<resultMap id="userResultMap" type="User">
<result property="id" column="id"/>
<result property="name" column="username"/>
<result property="pwd" column="password"/>
</resultMap>

<select id="getUserById" resultMap="userResultMap">
select * from user where id = #{id}
</select>

五、sql

1. 说明

用于定义可复用的 SQL 片段,以便在其它标签中使用。并且该 SQL 片段可以与 SQL 语句“拼接”使用。

2. 使用

1
2
3
<sql id="唯一标识">
···SQL片段···${参数名}···SQL片段···
</sql>
  • 使用 sql 标签定义 SQL 片段

  • id 用于唯一标识此片段

  • SQL 片段中可以放置参数占位符,待引用时再赋值

1
2
3
<include refid="唯一标识">
<property name="参数名" value="参数值"/>
</include>
  • 在需要使用 SQL 片段的地方用 include 标签引用
  • refid 属性填写唯一标识值
  • SQL 中需要赋值的参数通过 property 赋值

3. 示例

1
2
3
4
5
6
7
8
9
10
<sql id="test">
from ${table_name}
</sql>

<select id="getUserList" resultType="pojo.User">
select *
<include refid="test">
<property name="table_name" value="user"/>
</include>
</select>

参考