【-1】README
【0】SQL 映射文件有很少的几个顶级元素(按照它们应该被定义的顺序):
cache
– 给定命名空间的缓存配置。cache-ref
– 其他命名空间缓存配置的引用。resultMap
– 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。(干货——resultMap确实很厉害)parameterMap
– 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。sql
– 可被其他语句引用的可重用语句块。insert
– 映射插入语句update
– 映射更新语句delete
– 映射删除语句select
– 映射查询语句<select id="selectPerson" parameterType="int" resultType="hashmap"> SELECT * FROM PERSON WHERE ID = #{id} </select>
// Similar JDBC code, NOT 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="10000" fetchSize="256" statementType="PREPARED" resultSetType="FORWARD_ONLY">
<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">
<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>
2)插入语句的配置规则更加丰富,有多种生成方式
<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>
<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 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>
<insert id="insertUser" parameterType="User"> insert into users (id, username, password) values (#{id}, #{username}, #{password}) </insert>
对以上代码的分析(Analysis):如果 User 类型的参数对象传递到了语句中,id、username 和 password 属性将会被查找,然后将它们的值传入预处理语句的参数中。
2)这点对于向语句中传参是比较好的而且又简单,不过参数映射的功能远不止于此。
2.1)首先,像 MyBatis 的其他部分一样,参数也可以指定一个特殊的数据类型。
#{property,javaType=int,jdbcType=NUMERIC}
Attention) 如果 null 被当作值来传递,对于所有可能为空的列,JDBC Type 是需要的。你可以自己通过阅读预处理语句的 setNull() 方法的 JavaDocs 文档来研究这种情况。
#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}
#{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}
4)字符串替换:默认情况下,使用#{}格式的语法会导致 MyBatis 创建预处理语句属性并安全地设置值(比如?)。这样做更安全,更迅速,通常也是首选做法,不过有时你只是想直接在 SQL 语句中插入一个不改变的字符串。比如,像 ORDER BY,你可以这样来使用:
ORDER BY ${columnName}
【5】Result Maps
1)intro: ResultMap 的设计就是简单语句不需要明确的结果映射,而很多复杂语句确实需要描述它们 的关系。
2)看个荔枝: 简单映射语句的示例了,但没有明确的 resultMap。比如:
<select id="selectUsers" resultType="map"> select id, username, hashedPassword from some_table where id = #{id} </select>
对以上代码的分析(Analysis):这样一个语句简单作用于所有列被自动映射到 HashMap 的键上,这由 resultType 属性 指定。3)problem + solution:
3.1)problem:这在很多情况下是有用的,但是 HashMap 不能很好描述一个领域模型。
3.2)solution:那样你的应 用程序将会使用 JavaBeans 或 POJOs(Plain Old Java Objects,普通 Java 对象)来作为领域 模型。MyBatis 对两者都支持。看看下面这个 JavaBean:
package com.someapp.model; public class User { private int id; private String username; private String hashedPassword; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getHashedPassword() { return hashedPassword; } public void setHashedPassword(String hashedPassword) { this.hashedPassword = hashedPassword; } }
<select id="selectUsers" resultType="com.someapp.model.User"> select id, username, hashedPassword from some_table where id = #{id} </select>
<!-- In mybatis-config.xml file --> <typeAlias type="com.someapp.model.User" alias="User"/> <!-- In SQL Mapping XML file --> <select id="selectUsers" resultType="User"> select id, username, hashedPassword from some_table where id = #{id} </select>
4)MyBatis 会在幕后自动创建一个 ResultMap,基于属性名来映射列到 JavaBean 的属性上。
5)problem+solution:
5.1)problem:如果列名没有精确匹配;
5.2)solution:(2 solutions)
5.2.1)你可以在列名上使用 select 字句的别名(一个 基本的 SQL 特性)来匹配标签。比如:
<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>
<selectid="selectUsers"resultMap="userResultMap"> select user_id, user_name, hashed_password from some_table where id = #{id} </select>
【5.1】高级结果映射
1.1)problem:MyBatis 创建的一个想法:数据库不用永远是你想要的或需要它们是什么样的。而我们 最喜欢的数据库最好是第三范式或 BCNF 模式,但它们有时不是。如果可能有一个单独的 数据库映射,所有应用程序都可以使用它,这是非常好的,但有时也不是;1.2)solutions: 结果映射就是 MyBatis 提供处理这个问题的答案;
<!-- Very Complex Statement --> <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>
2.2)solutions:下面是一个完整的复杂结果映射例子 (假设作者, 博客, 博文, 评论和标签都是类型的别名) 我们来看看, 。 但是不用紧张, 我们会一步一步来说明。
<!-- Very Complex Result Map --> <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>
对以上代码的分析(Analysis):
constructor
- 类在实例化时,用来注入结果到构造方法中
idArg
-ID 参数;标记结果作为 ID 可以帮助提高整体效能arg
-注入到构造方法的一个普通结果id
–一个 ID 结果;标记结果作为 ID 可以帮助提高整体效能result
–注入到字段或 JavaBean 属性的普通结果association
–一个复杂的类型关联;许多结果将包成这种类型
collection
–复杂类型的集
discriminator
–使用结果值来决定使用哪个结果映射
case
–基于某些值的结果映射
<id property="id" column="post_id"/> <result property="subject" column="post_subject"/>
这两者之间的唯一不同是 id 表示的结果将是当比较对象实例时用到的标识属性。这帮 助来改进整体表现,特别是缓存和嵌入结果映射(也就是联合映射) 。
<constructor> <idArg column="id" javaType="int"/> <arg column="username" javaType="String"/> </constructor>
public class User { //... public User(int id, String username) { //... } //... }
A1)当创建一个构造方法元素时,保证参数是按顺序 排列的,而且数据类型也是确定的。
A2)该元素的属性值 和 result 和 id 元素的属性值相同;
<association property="author" column="blog_author_id" javaType="Author"> <id property="id" column="author_id"/> <result property="username" column="author_username"/> </association>
way1)嵌套查询:通过执行另外一个 SQL 映射语句来返回预期的复杂类型。way2)嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集。首先, 让我们来查看这个元素的属性。所有的你都会看到,它和普通的只由 select 和 resultMap 属性的结果映射不同。
【5.6】关联的嵌套查询
1)看个荔枝
<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>对以上代码的分析(Analysis): 我们有两个查询语句:一个来加载博客,另外一个来加载作者,而且博客的结果映射描 述了“selectAuthor”语句应该被用来加载它的 author 属性。
其他所有的属性将会被自动加载,假设它们的列和属性名相匹配。
这种方式很简单, 但是对于大型数据集合和列表将不会表现很好。 问题就是我们熟知的 “N+1 查询问题”。概括地讲,N+1 查询问题可以是这样引起的:(干货——引入了N+1 查询问题)
这个问题会导致成百上千的 SQL 语句被执行。这通常不是期望的。
MyBatis 能延迟加载这样的查询就是一个好处,因此你可以分散这些语句同时运行的消 耗。然而,如果你加载一个列表,之后迅速迭代来访问嵌套的数据,你会调用所有的延迟加 载,这样的行为可能是很糟糕的。
所以还有另外一种方法。(干货——引入了 延迟加载)
【5.7】关联的嵌套结果
<selectid="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><span style="color: rgb(0, 0, 136); font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; font-size: 16px; line-height: 24px; background-color: rgb(245, 245, 245);"> </span>
<resultMap id="blogResult" type="Blog"> <id property="id" column="blog_id" /> <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"/> </association> </resultMap>
2.1)调用作者 Author 的resultMap,定义如下:<selectid="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="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 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>
<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>
private List<Post> posts;
<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="Blog"> SELECT * FROM POST WHERE BLOG_ID = #{id} </select>
<collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
<collection property="posts" column="id" ofType="Post" select="selectPostsForBlog"/>
<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>
2.1)我们又一次联合了博客表和文章表,而且关注于保证特性,结果列标签的简单映射。现 在用文章映射集合映射博客,可以简单写为:
<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>2.2)同样,要记得 id 元素的重要性,如果你不记得了,请阅读上面的关联部分。
2.3)同样, 如果你引用更长的形式允许你的结果映射的更多重用, 你可以使用下面这个替代 的映射:
<resultMap id="blogResult" type="Blog"> <id property="id" column="blog_id" /> <result property="title" column="blog_title"/> <collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/> </resultMap> <resultMap id="blogPostResult" type="Post"> <id property="id" column="id"/> <result property="subject" column="subject"/> <result property="body" column="body"/> </resultMap>
<discriminator javaType="int" column="draft"> <case value="1" resultType="DraftPost"/> </discriminator>
<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>
A1) 在这个示例中, MyBatis 会从结果集中得到每条记录, 然后比较它的 vehicle 类型的值。如果它匹配任何一个鉴别器的实例,那么就使用这个实例指定的结果映射。
A2)如果没有任何 一个实例相匹配,那么 MyBatis 仅仅使用鉴别器块外定义的结果映射。所以,如果 carResult 按如下声明:<resultMap id="carResult" type="Car"> <result property="doorCount" column="door_count" /> </resultMap>
A3)那么只有 doorCount 属性会被加载。这步完成后完整地允许鉴别器实例的独立组,尽管 和父结果映射可能没有什么关系。这种情况下,我们当然知道 cars 和 vehicles 之间有关系, 如 Car 是一个 Vehicle 实例。因此,我们想要剩余的属性也被加载。我们设置的结果映射的 简单改变如下。(干货——想要剩余的属性也被加载,使用继承extend)<resultMap id="carResult" type="Car" extends="vehicleResult"> <result property="doorCount" column="door_count" /> </resultMap>A4)现在 vehicleResult 和 carResult 的属性都会被加载了。
3)尽管曾经有些人会发现这个外部映射定义会多少有一些令人厌烦之处。 因此还有另外一 种语法来做简洁的映射风格。比如:
<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" resultType="carResult"> <result property="doorCount" column="door_count" /> </case> <case value="2" resultType="truckResult"> <result property="boxSize" column="box_size" /> <result property="extendedCab" column="extended_cab" /> </case> <case value="3" resultType="vanResult"> <result property="powerSlidingDoor" column="power_sliding_door" /> </case> <case value="4" resultType="suvResult"> <result property="allWheelDrive" column="all_wheel_drive" /> </case> </discriminator> </resultMap>
【6】自动映射
1)intro: 通常数据库列使用大写单词命名,单词间用下划线分隔;而java属性一般遵循驼峰命名法。 为了在这两种命名方式之间启用自动映射,需要将 mapUnderscoreToCamelCase
设置为true。
2)自动映射甚至在特定的result map下也能工作。在这种情况下,对于每一个result map,所有的ResultSet提供的列, 如果没有被手工映射,则将被自动映射。自动映射处理完毕后手工映射才会被处理。 在接下来的例子中, id 和 userName列将被自动映射, hashed_password 列将根据配置映射。(干货——自动映射处理完毕后手工映射才会被处理)
3)看个荔枝
<selectid="selectUsers"resultMap="userResultMap"> select user_id as "id", user_name as "userName", hashed_password from some_table where id = #{id} </select>
4)There are three auto-mapping levels:(3个自动映射级别)<resultMap id="userResultMap" type="User"> <result property="password" column="hashed_password"/> </resultMap>
NONE
- disables auto-mapping. Only manually mapped properties will be set.PARTIAL
- will auto-map results except those that have nested result mappings defined inside (joins).(默认级别)FULL
- auto-maps everything.4.1)将PARTIAL 设置为默认级别的原因:The default value is PARTIAL
, and it is so for a reason. When FULL
is used auto-mapping will be performed when processing join results and joins retrieve data of several different entities in the same row hence this may result in undesired mappings.
4.2)To understand the risk have a look at the following sample:
<selectid="selectBlog"resultMap="blogResult"> select B.id, B.title, A.username, from Blog B left outer join Author A on B.author_id = A.id 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>
Analysis of the sample) With this result map both Blog and Author will be auto-mapped. But note that Author has an id property and there is a column named id in the ResultSet so Author's id will be filled with Blog's id, and that is not what you were expecting. So use the FULL
option with caution.
4.3)Regardless of the auto-mapping level configured you can enable or disable the automapping for an specific ResultMap by adding the attribute autoMapping
to it: (干货——添加 autoMapping属性)
<pre name="code" class="html"><resultMap id="userResultMap" type="User" autoMapping="false"> <result property="password" column="hashed_password"/> </resultMap>
【7】缓存
1)intro: 默认情况下是没有开启缓存的,除了局部的 session 缓存,可以增强变现而且处理循环 依赖也是必须的。要开启二级缓存,你需要在你的 SQL 映射文件中添加一行:
<cache/>对以上代码的分析(Analysis): 这个简单语句的效果如下:
<pre name="code" class="html"><cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
LRU
– 最近最少使用的:移除最长时间不被使用的对象。(默认策略)FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。SOFT
– 软引用:移除基于垃圾回收器状态和软引用规则的对象。WEAK
– 弱引用: 更积极地移除基于垃圾收集器状态和弱引用规则的对象。
Supplement)
supplement1)flushInterval(刷新间隔):可以被设置为任意的正整数,而且它们代表一个合理的毫秒 形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
supplement2)size(引用数目):可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的 可用内存资源数目。默认值是 1024。
supplement3)readOnly(只读)属性: 可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓 存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存 会返回缓存对象的拷贝(通过序列化) 。这会慢一些,但是安全,因此默认是 false。
【7.1】使用自定义缓存
1)intro: 你也可以通过实现你自己的缓存或为其他第三方缓存方案 创建适配器来完全覆盖缓存行为。(干货——自定义缓存)
<cache type="com.domain.something.MyCustomCache"/>
对以上代码的分析(Analysis):
A1)type 属 性指 定的 类必 须实现 org.mybatis.cache.Cache 接口。
public interface Cache { String getId(); int getSize(); void putObject(Object key, Object value); Object getObject(Object key); boolean hasKey(Object key); Object removeObject(Object key); void clear(); }
<cache type="com.domain.something.MyCustomCache"> <property name="cacheFile" value="/tmp/my-custom-cache.tmp"/> </cache>
3)记得缓存配置和缓存实例是绑定在 SQL 映射文件的命名空间是很重要的。因此,所有 在相同命名空间的语句正如绑定的缓存一样。 语句可以修改和缓存交互的方式, 或在语句的 语句的基础上使用两种简单的属性来完全排除它们。默认情况下,语句可以这样来配置:
<select ... flushCache="false" useCache="true"/> <insert ... flushCache="true"/> <update ... flushCache="true"/> <delete ... flushCache="true"/>
对以上代码的分析(Analysis) : 因为那些是默认的,你明显不能明确地以这种方式来配置一条语句。相反,如果你想改 变默认的行为,只能设置 flushCache 和 useCache 属性。比如,在一些情况下你也许想排除 从缓存中查询特定语句结果,或者你也许想要一个查询语句来刷新缓存。相似地,你也许有 一些更新语句依靠执行而不需要刷新缓存。(干货——如果你想改 变默认的行为,只能设置 flushCache 和 useCache 属性)
cache-refnamespace="com.someone.application.data.SomeMapper"/>