Mybatis官方文档-XML映射器(mapper)
MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。
顶级元素:
cache– 该命名空间的缓存配置。cache-ref– 引用其它命名空间的缓存配置。resultMap– 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。parameterMap– 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。sql– 可被其它语句引用的可重用语句块。insert– 映射插入语句。update– 映射更新语句。delete– 映射删除语句。select– 映射查询语句。
select
MyBatis 的基本原则之一是:在每个插入、更新或删除操作之间,通常会执行多个查询操作。
一个简单的例子:
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
</select>
其中需要注意的是#{},这个和在mybaits-config.xml中的${}的区别,在mapper中#{}是个占位符,这就相当于告诉mybatis创建一个preparedstatement,参数会用?替换
// 近似的 JDBC 代码,非 MyBatis 代码...
String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);
配置语句
<select
id="selectPerson"
parameterType="int"
parameterMap="deprecated"
resultType="hashmap"
resultMap="personResultMap"
flushCache="false"
useCache="true"
timeout="10"
fetchSize="256"
statementType="PREPARED"
resultSetType="FORWARD_ONLY">
-
id:命名空间里面的唯一标识符,不同命名空间就可以一样,这就导致不能重载
-
parameterType:将会传入这条语句的参数的类全限定名或别名。
-
resultType:期望从这条语句中返回结果的类全限定名或别名。注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。resultType 和 resultMap 之间只能同时使用一个。
-
resultMap:对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。
-
flushCache:将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。
-
useCache:将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
-
statementType:可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
insert, update 和 delete
<insert
id="insertAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
keyProperty=""
keyColumn=""
useGeneratedKeys=""
timeout="20">
<update
id="updateAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">
<delete
id="deleteAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">
这里面的参数和上面的很相似就不多说了,需要说一个useGeneratedKeys,这个就是让数据库自己产生主键。
示例:
<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>
对于上面的insert语句,如果数据库开启了例如主键递增,那么就可以进行进一步简
<insert id="insertAuthor" useGeneratedKeys="true"
keyProperty="id">
insert into Author (username,password,email,bio)
values (#{username},#{password},#{email},#{bio})
</insert>
通过设置useGeneratedKeys为true可以直接使用数据库自动递增。
mybatis还支持多行插入,需要使用到
<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>
对于不能够自动生成主键的数据库mybaits也有办法(不推荐使用)。就是使用selectKey来进行生成key
<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>
- keyProperty:要生成的id
- resultType:返回类型
- order:可以设置为
BEFORE或AFTER。如果设置为BEFORE,那么它首先会生成主键,设置keyProperty再执行插入语句。如果设置为AFTER,那么先执行插入语句,然后是selectKey中的语句
sql
这个标签元素是为了重用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>
是通过id来进行引用的。
可以套娃来用,就是sql里面放include,去引用其他的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>
参数
JDBC 要求,如果一个列允许使用 null 值,并且会使用值为 null 的参数,就必须要指定 JDBC 类型(jdbcType)。
字符串替换
在mybaits中使用#{}来作为占位符,当要创建preparedstatement的时候,会把这个占位符转换成?,但是有时候如果想在SQL中添加一个不转义的字符就需要${}这个方法了,(这个和配置文件的一样)
例如,要查询下面三个语句:
@Select("select * from user where id = #{id}")
User findById(@Param("id") long id);
@Select("select * from user where name = #{name}")
User findByName(@Param("name") String name);
@Select("select * from user where email = #{email}")
User findByEmail(@Param("email") String email);
其中基本一样,唯一的不同就是id name email这几个字段,这个时候就可使用一个sql完成。
@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);
第一个参数是${}第二个是#{}
注意!用这种方式接受用户的输入,并用作语句参数是不安全的,会导致潜在的 SQL 注入攻击。因此,要么不允许用户输入这些字段,要么自行转义并检验这些参数。
结果映射(重点,难点)
resultMap 元素是 MyBatis 中最重要最强大的元素。
在一般的情况下,比如你的ResultType是User类,那么你并没有写resultMap但是却可以匹配上(要求名字一致),如果列名和属性不一致,那就需要匹配:
- 使用别名
- 使用resultMap
<select id="selectUsers" resultType="User">
select
user_id as "id",
user_name as "userName",
hashed_password as "hashedPassword"
from some_table
where id = #{id}
</select>
<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="selectBlogDetails" resultMap="detailedBlogResultMap">
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,
A.favourite_section as author_favourite_section,
P.id as post_id,
P.blog_id as post_blog_id,
P.author_id as post_author_id,
P.created_on as post_created_on,
P.section as post_section,
P.subject as post_subject,
P.draft as draft,
P.body as post_body,
C.id as comment_id,
C.post_id as comment_post_id,
C.name as comment_name,
C.comment as comment_text,
T.id as tag_id,
T.name as tag_name
from Blog B
left outer join Author A on B.author_id = A.id
left outer join Post P on B.id = P.blog_id
left outer join Comment C on P.id = C.post_id
left outer join Post_Tag PT on PT.post_id = P.id
left outer join Tag T on PT.tag_id = T.id
where B.id = #{id}
</select>
<!-- 非常复杂的结果映射 -->
<resultMap id="detailedBlogResultMap" type="Blog">
<constructor>
<idArg column="blog_id" javaType="int"/>
</constructor>
<result property="title" column="blog_title"/>
<association property="author" javaType="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"/>
<result property="favouriteSection" column="author_favourite_section"/>
</association>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<association property="author" javaType="Author"/>
<collection property="comments" ofType="Comment">
<id property="id" column="comment_id"/>
</collection>
<collection property="tags" ofType="Tag" >
<id property="id" column="tag_id"/>
</collection>
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
</discriminator>
</collection>
</resultMap>
首先需要搞懂上面的标签的意思:
- constructor用于在实例化类时,注入结果到构造方法中
idArg- ID 参数;标记出作为 ID 的结果可以帮助提高整体性能arg- 将被注入到构造方法的一个普通结果
- id:一个ID结果,可以提升性能
- result:一个正常的映射,一般就是列明和属性名不一样
- association:一对一,在association里面可以包含resultMap,一般是一个类和另一个类关联,需要查出来另一个类,这就需要映射,就像博客和作者的关系
- collection:一对多,其中的ofType是指的具体的类的类型,也可以使用resultMap
- discriminator:使用结果值来决定使用哪个
resultMapcase– 基于某些值的结果映射
最佳实践:
最好逐步建立结果映射。单元测试可以在这个过程中起到很大帮助。 如果你尝试一次性创建像上面示例那么巨大的结果映射,不仅容易出错,难度也会直线上升。 所以,从最简单的形态开始,逐步迭代。而且别忘了单元测试! 有时候,框架的行为像是一个黑盒子(无论是否开源)。因此,为了确保实现的行为与你的期望相一致,最好编写单元测试。 并且单元测试在提交 bug 时也能起到很大的作用。
id & result
这两者之间的唯一不同是,id 元素对应的属性会被标记为对象的标识符,在比较对象实例时使用。 这样可以提高整体的性能,尤其是进行缓存和嵌套结果映射(也就是连接映射)的时候。
构造方法
通过修改对象属性的方式,可以满足大多数的数据传输对象(Data Transfer Object, DTO)以及绝大部分领域模型的要求。
public class User {
//...
public User(Integer id, String username, int age) {
//...
}
//...
}
<constructor>
<idArg column="id" javaType="int"/>
<arg column="username" javaType="String"/>
<arg column="age" javaType="_int"/>
</constructor>
关联
<association property="author" column="blog_author_id" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
</association>
关联(association)元素处理“有一个”类型的关系。 比如一个博客有一个用户。
mybatis中有两种方法加载关联:
-
嵌套select查询(效率低下):通过执行另一个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 = #{id} </select>比如这个例子,首先要查出博客,然后根据博客的author_id来查出作者,两个过程是分开的,如果第一次查出了N个结果,那么还有对这N个进行N次查询,这也就叫N+1查询。这个问题会导致成百上千的 SQL 语句被执行。有时候,我们不希望产生这样的后果。虽然有延迟加载可以缓解,但是,如果查询出来就要遍历,那么就会触发延迟加载。
注意下其中的参数:
- property:就是属性名字,比如在Blog里面有个author的引用,就需要这个。
- column这是数据库列名,这个数据会传递给等下要进行的select语句。
- javaType
-
关联的嵌套结果映射
<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" column="blog_author_id" javaType="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>比如上面这个就是一个关联的结果嵌套映射,首先,只执行了一次查询,再根据查询的结果进行进一步的结果映射,上面的resultMap单独提出来了,是为了复用,如果不复用当然也可以写到associasion标签里面。
参数:
- columnPrefix:当连接多个表时,你可能会不得不使用列别名来避免在
ResultSet中产生重复的列名。指定 columnPrefix 列名前缀允许你将带有这些前缀的列映射到一个外部的结果映射中。 - notNullColumn:默认情况下,在至少一个被映射到属性的列不为空时,子对象才会被创建。 你可以在这个属性上指定非空的列来改变默认行为,指定后,Mybatis 将只在这些列非空时才创建一个子对象。
Tips:
id 元素在嵌套结果映射中扮演着非常重要的角色。你应该总是指定一个或多个可以唯一标识结果的属性。 虽然,即使不指定这个属性,MyBatis 仍然可以工作,但是会产生严重的性能问题。 只需要指定可以唯一标识结果的最少属性。显然,你可以选择主键(复合主键也可以)。
- columnPrefix:当连接多个表时,你可能会不得不使用列别名来避免在
上面的问题,如果出现共同作者怎么办?
<select id="selectBlog" resultMap="blogResult">
select
B.id as blog_id,
B.title as blog_title,
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,
CA.id as co_author_id,
CA.username as co_author_username,
CA.password as co_author_password,
CA.email as co_author_email,
CA.bio as co_author_bio
from Blog B
left outer join Author A on B.author_id = A.id
left outer join Author CA on B.co_author_id = CA.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" />
<association property="coAuthor"
resultMap="authorResult"
columnPrefix="co_" />
</resultMap>
这个就需要使用到前面说的columnPrefix参数了。
关联的多结果集(ResultSet)
| 属性 | 描述 |
|---|---|
column |
当使用多个结果集时,该属性指定结果集中用于与 foreignColumn 匹配的列(多个列名以逗号隔开),以识别关系中的父类型与子类型。 |
foreignColumn |
指定外键对应的列名,指定的列将与父类型中 column 的给出的列进行匹配。 |
resultSet |
指定用于加载复杂类型的结果集名字。 |
某些数据库允许存储过程返回多个结果集,或一次性执行多个语句,每个语句返回一个结果集。 我们可以利用这个特性,在不使用连接的情况下,只访问数据库一次就能获得相关数据。
SELECT * FROM BLOG WHERE ID = #{id}
SELECT * FROM AUTHOR WHERE ID = #{id}
在映射语句中,必须通过 resultSets 属性为每个结果集指定一个名字,多个名字使用逗号隔开。
<select id="selectBlog" resultSets="blogs,authors" resultMap="blogResult" statementType="CALLABLE">
{call getBlogsAndAuthors(#{id,jdbcType=INTEGER,mode=IN})}
</select>
<resultMap id="blogResult" type="Blog">
<id property="id" column="id" />
<result property="title" column="title"/>
<association property="author" javaType="Author" resultSet="authors" column="author_id" foreignColumn="id">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="email" column="email"/>
<result property="bio" column="bio"/>
</association>
</resultMap>
集合
<collection property="posts" ofType="domain.blog.Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<result property="body" column="post_body"/>
</collection>
集合和关联特别的像,但是需要主要的是使用的ofType,这个ofType指的list中存放的实例的类型比如List
对应的方法也有三种:
-
集合嵌套select
<resultMap id="blogResult" type="Blog"> <collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/> </resultMap> <select id="selectBlog" resultMap="blogResult"> SELECT * FROM BLOG WHERE ID = #{id} </select> <select id="selectPostsForBlog" resultType="Post"> SELECT * FROM POST WHERE BLOG_ID = #{id} </select> -
集合嵌套结果映射
<select id="selectBlog" resultMap="blogResult"> select B.id as blog_id, B.title as blog_title, B.author_id as blog_author_id, P.id as post_id, P.subject as post_subject, P.body as post_body, from Blog B left outer join Post P on B.id = P.blog_id where B.id = #{id} </select>resultMap id="blogResult" type="Blog"> <id property="id" column="blog_id" /> <result property="title" column="blog_title"/> <collection property="posts" ofType="Post"> <id property="id" column="post_id"/> <result property="subject" column="post_subject"/> <result property="body" column="post_body"/> </collection> </resultMap> -
集合的多结果集(ResultSet)
SELECT * FROM BLOG WHERE ID = #{id} SELECT * FROM POST WHERE BLOG_ID = #{id}<select id="selectBlog" resultSets="blogs,posts" resultMap="blogResult"> {call getBlogsAndPosts(#{id,jdbcType=INTEGER,mode=IN})} </select>
鉴别器
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
</discriminator>
鉴别器的概念很好理解——它很像 Java 语言中的 switch 语句。
用法:
<resultMap id="vehicleResult" type="Vehicle">
<id property="id" column="id" />
<result property="vin" column="vin"/>
<result property="year" column="year"/>
<result property="make" column="make"/>
<result property="model" column="model"/>
<result property="color" column="color"/>
<discriminator javaType="int" column="vehicle_type">
<case value="1" resultMap="carResult"/>
<case value="2" resultMap="truckResult"/>
<case value="3" resultMap="vanResult"/>
<case value="4" resultMap="suvResult"/>
</discriminator>
</resultMap>
自动映射
在简单的场景下,MyBatis 可以为你自动映射查询结果。但如果遇到复杂的场景,你需要构建一个结果映射。这一节将展示如何混合使用
如果数据库使用了下划线命名,并且Java中遵循了驼峰命名,那么可以设置mapUnderscoreToCamelCase为true开启驼峰命名自动映射。
自动和配置可以并存的如下:
<select id="selectUsers" resultMap="userResultMap">
select
user_id as "id",
user_name as "userName",
hashed_password
from some_table
where id = #{id}
</select>
<resultMap id="userResultMap" type="User">
<result property="password" column="hashed_password"/>
</resultMap>
有三种级别:
- NONE:不自动映射
- PARTIAL:部分自动映射,对除在内部定义了嵌套结果(也就是连接的属性)映射以外的属性进行映射
- FULL:全自动映射(会出问题)
<select id="selectBlog" resultMap="blogResult">
select
B.id,
B.title,
A.username,
from Blog B left outer join Author A on B.author_id = A.ids
where B.id = #{id}
</select>
<resultMap id="blogResult" type="Blog">
<association property="author" resultMap="authorResult"/>
</resultMap>
<resultMap id="authorResult" type="Author">
<result property="username" column="author_username"/>
</resultMap>
比如上面这个例子,如果全部自动映射,那么就会有两个id,一个是博客的一个是Author的,可能填错,慎用!
缓存
MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。
默认情况下只开启了本地缓存,它仅仅对一个会话中的数据进行缓存,要开用全局的二级缓存需要再SQL映射文件(xxxxMapper.xml)中添加一个标签
这一个标签代表的意思如下:
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。(如果共享会提高读的效率)
Tips:缓存只作用于 cache 标签所在的映射文件中的语句。
常见配置参数:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
上面代表60秒刷新,存储最多512个引用,只读。
一般可用的策略有:
- LRU (默认)
- FIFO
- SOFT 软引用:基于垃圾回收器状态和软引用规则移除对象
- WEAK 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象
浙公网安备 33010602011771号