MyBatis Mapper XML文件详解

MyBatis Mapper XML文件详解

首先需要澄清几个概念:

  • namespace 就是Mapper XML对应的Java接口名.
  • 联合查询: 也就是多表的各种join查询
  • 关联映射: 也就是嵌套映射, 用在一个POJO中包含其他POJO的情况

Mapper XML顶级配置元素

  • cache 启用本xml对应的namespace的二级缓存
  • cache-ref 共享指定namespace的二级缓存
  • resultMap (强制配置字段)进行数据库列和返回Java对象的属性之间的映射
  • sql 可被其他语句引用的可重用sql语句块(地位相当于Java中的private辅助方法或常量)
  • insert/delete/update/select 增删改查映射语句

select

<select id="selectPerson" parameterType="int" resultType="hashmap">
  SELECT * FROM PERSON WHERE ID = #{id}
select>

以上语句完成的是将符合条件的所有列返回, 以key-value对的形式保存在HashMap类型的对象中.

select子句需要注意的属性有:

属性 描述
id 与Java接口的方法名对应
resultType 如果sql语句返回结果能够对应到Java的基本类型或者集合类型, 使用此属性进行配置
resultMap 当sql语句返回结果与某个Domain Object对应时, 使用此属性进行描述, 推荐同时强制设定一个顶级配置元素resultMap进行Java对象和表列之间的映射
flushCache true, 每次调用都会导致本地缓存和二级缓存整体清空; 默认是false, 也就是使用缓存. 通常对于增删改类语句配置此属性
useCache true, 指定结果使用二级缓存, select子句默认启用
timeout 在抛出异常之前, 驱动程序等待数据库返回结果的秒数, 默认继承数据库驱动的配置
fetchSize 尝试指定批量返回结果行数, 默认继承驱动的配置
databaseId 尝试使用mybatis配置文件中与该id对应的databaseIdProvider提供的主键生成策略, 与动态sql结合, 能够达到适配不同数据库的需求, 增加可移植性

关于select子句, 有以下几点要特别强调的:

  • parameterType通常无须指定, 因为可以根据namespace对应的Java接口方法推断出来
  • 每个resultMap推荐强制在之前进行resultMap的声明, 这样当修改了数据库或Java Domain Object的时候, 这里能够快速报错, 方便问题定位.
  • select子句务必指明所有需要的列, 避免额外的数据传输. 即使是需要全部列也不要用’*’, 因为会增加数据库的解析成本(唯一允许用’*'的地方是count(*)).

insert/delete/update

部分属性与select子句是通用的, 需要额外注意的属性有:

属性 描述
useGeneratedKeys (仅对insert/update生效), 告知MyBatis使用JDBC的getGeneratedKeys方法来取出数据库内部生成的自增型主键, 适用于MySQL, SQL Server等, 默认为false

以下是几个例子:


<insert id="insertAuthor" useGeneratedKeys="true"
    keyProperty="id">
  insert into Author (username,password,email,bio)
  values (#{username},#{password},#{email},#{bio})
insert>


<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>


<insert id="insertAuthor">
  <selectKey keyProperty="id" resultType="int" order="BEFORE">
    select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
  selectKey>
  insert into Author
    (id, username, password, email,bio, favourite_section)
  values
    (#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR})
insert>

sql

一个例子:


<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password sql>


<select id="selectUsers" resultType="map">
  select
    <include refid="userColumns"><property name="alias" value="t1"/>include>,
    <include refid="userColumns"><property name="alias" value="t2"/>include>
  from some_table t1
    cross join some_table t2
select>

下面是一个嵌套sql方法的例子:

<sql id="sometable">
  ${prefix}Table
sql>

<sql id="someinclude">
  from
    <include refid="${include_target}"/>
sql>

<select id="select" resultType="map">
  select
    field1, field2, field3
  <include refid="someinclude">
    <property name="prefix" value="Some"/>
    <property name="include_target" value="sometable"/>
  include>
select>

resultMap–结果映射

我们约定强制对所有的select子句都需要显式设定resultMap属性, 因为显式对结果映射进行声明, 有利于后期修改数据库(例如修改列名或增加字段等场景)能够迅速定位异常出现的原因, 对于后期维护节约的成本远远低于声明一个resultMap的成本.

一个符合上述描述标准的简单例子:


<resultMap id="userResultMap" type="User">
  <id property="id" column="user_id" />
  <result property="username" column="user_name"/>
  <result property="password" column="hashed_password"/>
resultMap>


<select id="selectUsers" resultMap="userResultMap">
  select user_id, user_name, hashed_password
  from some_table
  where id = #{id}
select>

枚举类型映射

遵循以下法则即可:

  • 数据库中字段直接存枚举的类型名为char或者varchar即可, 例如ERROR;
  • Domain Object中相应字段类型直接用枚举类型;

这样select的时候会自动匹配的.

高级结果映射

当我们的结果映射包含嵌套实体映射的时候, 我们需要启用resultMap的子元素辅助进行嵌套实体映射.

下面我们来介绍resultMap的子元素

id & result

这两个都是用来映射一个简单结果列, 如果是数据库中的id, 那么最好还是使用id, 因为能提高性能(主要是缓存性能和联合查询join的性能).

这两个元素有以下值得关注的属性:

属性 描述
property (必选)JavaBean中的属性名
column (必选)数据库中的列
jdbcType 如果数据库中的列允许空值, 需要显式配置此属性, 因为列的数据类型不同, 对于空值的处理是不同的, 可用的类型如下表

支持的jdbcType:

支持的jdbcType

constructor

constructor元素的使用场景是当一个POJO的一些属性是private final的或者是private且不提供公有getter的时候, 这时候只能够通过类的构造方法进行注入, constructor元素可以关联到指定type的POJO的相同参数构造方法上, 进行构造注入.

下面是一个例子(since mybatis 3.4.3, 之前的版本参数顺序必须与构造方法参数顺序严格对应):

public class User {
   //...
   public User(@Param("id") Integer id, @Param("username") String username, @Param("age") int age) {
     //...
  }
//...
}

对应的Mapper XML片段如下:

<constructor>
   <idArg column="id" javaType="int" name="id" />
   <arg column="age" javaType="_int" name="age" />
   <arg column="username" javaType="String" name="username" />
constructor>

assosiation–解决has one类型的关联查询

assosiation是用来做关联映射的, 如果POJO中有嵌套的POJO定义, 使用这个元素获取嵌套的结果. 注意: assosiation本身也可以作为一种resultMap.

关联映射可以使用MyBatis提供的**延迟加载(Lazy Loading)**特性, 也就是说, 如果外层POJO被查询后, 嵌套的POJO如果没有被使用, 那么与之关联的select子句不会被执行, 直到被使用的一刻才会执行.

注意: 延迟加载是为了解决著名的"N+1查询问题"而设定的, 所谓N+1查询问题, 就是说1个外层实体, 可能内嵌N个实体, 外层的一个查询实际会引发"N+1"次查询, 这对于大数据集来说效率是不可接受的, 所以不要执行对大数据集批量返回后紧接着在代码里遍历所有嵌套字段的操作, 这会使延迟加载的特性失效! 此种情况请考虑设计库表添加冗余字段.

例1: (不推荐, 请使用例3做法)一个简单的关联查询, 直接将association作为resultMap使用, 用于联合查询的结果列映射.

<association property="author" javaType="Author">
  <id property="id" column="author_id"/>
  <result property="username" column="author_username"/>
association>

需要关注的属性有:

属性 描述
property (必选)POJO中属性名
javaType (必选)POJO类名
column (必选)对应库表的列名, 或者列名集(如果是复合key的话需要这样), 会作为嵌套select查询的参数传入, 例如: column=“author_id”, column="{param1=col1,param2=col2}"
select 嵌套子查询, 可以使用column中传入的参数
fetchType lazy, 延迟加载;eager, 立即加载; 需要全局的lazyLoadingEnabled开启支持
jdbcType 如果数据库中的列允许空值, 需要显式配置此属性, 因为列的数据类型不同, 对于空值的处理是不同的, 支持的类型见上表
columnPrefix 如果association重用了一个resultMap的话, 可以用这个标签快速引用联合查询的别名列, 详见[1]

例2: (推荐用法)一个复杂点的例子, 使用select属性进行关联查询:

<resultMap id="blogResult" type="Blog">
  <association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
resultMap>

<select id="selectBlog" resultMap="blogResult">
  SELECT * FROM BLOG WHERE ID = #{id}
select>

<select id="selectAuthor" resultType="Author">
  SELECT * FROM AUTHOR WHERE ID = #{author_id}
select>

例3: (推荐)一个复杂点的例子, 使用association关联一个resultMap的id进行联合查询结果列映射, 这样单独抽出来的resultMap, 可以重用.


<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" 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>

collection–解决has many类型的关联查询

基本用法与association一致.

例1: 一个简单的例子:

// Blog这个POJO中包含这么一个post list
private List<Post> posts;

<select id="selectBlog" resultMap="blogResult">
...
select>


<resultMap id="blogResult" type="domain.blog.Blog">
...

...
resultMap>


<select id="selectPostsForBlog" resultType="Post">
  SELECT * FROM POST WHERE BLOG_ID = #{id}
select>


<resultMap id="post" type="domain.blog.Post">
    <id property="id" column="post_id"/>
    <result property="subject" column="post_subject"/>
    <result property="body" column="post_body"/>
resultMap>

参考链接:

  • MyBatis官方文档–Mapper XML文件中文版
  • MyBatis官方文档–Mapper XML文件英文版

你可能感兴趣的:(MyBatis)