首先需要澄清几个概念:
namespace
就是Mapper XML对应的Java接口名.cache
启用本xml对应的namespace的二级缓存cache-ref
共享指定namespace的二级缓存resultMap
(强制配置字段)进行数据库列和返回Java对象的属性之间的映射sql
可被其他语句引用的可重用sql语句块(地位相当于Java中的private辅助方法或常量)insert/delete/update/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(*)
).部分属性与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 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>
我们约定强制对所有的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
;这样select
的时候会自动匹配的.
当我们的结果映射包含嵌套实体映射的时候, 我们需要启用resultMap的子元素辅助进行嵌套实体映射.
下面我们来介绍resultMap的子元素
id & result
这两个都是用来映射一个简单结果列, 如果是数据库中的id, 那么最好还是使用id, 因为能提高性能(主要是缓存性能和联合查询join的性能).
这两个元素有以下值得关注的属性:
属性 | 描述 |
---|---|
property | (必选)JavaBean中的属性名 |
column | (必选)数据库中的列 |
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>
参考链接: