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对象和表列之间的映射
flushCachetrue, 每次调用都会导致本地缓存和二级缓存整体清空; 默认是false, 也就是使用缓存. 通常对于增删改类语句配置此属性
useCachetrue, 指定结果使用二级缓存, 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

以下是几个例子:

<!--使用主键生成策略获取自增长型id-->
<insert id="insertAuthor" useGeneratedKeys="true"
    keyProperty="id">
  insert into Author (username,password,email,bio)
  values (#{username},#{password},#{email},#{bio})
</insert>

<!--结合动态SQL实现多行插入(需要数据库支持)-->
<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>

<!--使用自定义的主键生成策略首先生成主键(可以根据resultType类型灵活对应主键类型), 然后执行插入操作-->
<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语句(注意这里是钱字号, 代表常量, 我们这里在sql被include的时候会显式赋值, 所以不用担心sql注入的风险, 普通的井字号是接收变量, 也就相当于在statement中添加了一个问号占位符), 相当于定义了一个方法, 可以接收调用者传入的参数-->
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>

<!--在其他代码中引用, alias参数被赋值为不同的实际值-->
<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声明-->
<resultMap id="userResultMap" type="User">
  <id property="id" column="user_id" />
  <result property="username" column="user_name"/>
  <result property="password" column="hashed_password"/>
</resultMap>

<!--根据id使用显式声明的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中传入的参数
fetchTypelazy, 延迟加载;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>

<!--用于描述association的结果映射-->
<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">
...
<collection property="posts" column = "id" select="selectPostsForBlog" />
...
</resultMap>

<!--嵌套select-->
<select id="selectPostsForBlog" resultType="Post">
  SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>

<!--嵌套select对应的resultMap-->
<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>

参考链接:


  • 11
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值