MyBatis的建立基于这样一个思想:数据库并非您想怎样就怎样的。虽然咱们但愿全部的数据库遵照第三范式或BCNF(修正的第三范式),但它们不是。若是有一个数据库可以完美映射到全部应用程序,也将是很是棒的,但也没有。结果集映射就是MyBatis为解决这些问题而提供的解决方案。html
resultMapjava
·constructor–实例化的时候经过构造器将结果集注入到类中算法
oidArg– ID 参数; 将结果集标记为ID,以方便全局调用sql
oarg–注入构造器的结果集数据库
·id–结果集ID,将结果集标记为ID,以方便全局调用缓存
·result–注入一个字段或者javabean属性的结果安全
·association–复杂类型联合;许多查询结果合成这个类型mybatis
o嵌套结果映射– associations能引用自身,或者从其它地方引用,app
·collection–复杂类型集合框架
o嵌套结果映射– collections能引用自身,或者从其它地方引用
·discriminator–使用一个结果值以决定使用哪一个resultMap
ocase–基于不一样值的结果映射
§嵌套结果映射–case也能引用它自身, 因此也能包含这些一样的元素。它也能够从外部引用resultMap
注意:
public class A{
private B b1;
private List<B> b2;
}
在映射b1属性时用association标签, 映射b2时用collection标签,分别是一对一,一对多的关系
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
这是最基本的结果集映射。id 和result 将列映射到属性或简单的数据类型字段(String, int, double, Date等)。
这二者惟一不一样的是,在比较对象实例时id 做为结果集的标识属性。这有助于提升整体性能,特别是应用缓存和嵌套结果映射的时候。
Id、result属性以下:
Attribute |
Description |
property |
映射数据库列的字段或属性。若是JavaBean 的属性与给定的名称匹配,就会使用匹配的名字。不然,MyBatis 将搜索给定名称的字段。两种状况下您均可以使用逗点的属性形式。好比,您能够映射到“username”,也能够映射到“address.street.number”。 |
column |
数据库的列名或者列标签别名。与传递给resultSet.getString(columnName)的参数名称相同。 |
javaType |
完整java类名或别名(参考上面的内置别名列表)。若是映射到一个JavaBean,那MyBatis 一般会自行检测到。然而,若是映射到一个HashMap,那您应该明确指定javaType 来确保所需行为。 |
jdbcType |
这张表下面支持的JDBC类型列表列出的JDBC类型。这个属性只在insert,update或delete 的时候针对容许空的列有用。JDBC 须要这项,但MyBatis 不须要。若是您直接编写JDBC代码,在容许为空值的状况下须要指定这个类型。 |
typeHandler |
咱们已经在文档中讨论过默认类型处理器。使用这个属性能够重写默认类型处理器。它的值能够是一个TypeHandler实现的完整类名,也能够是一个类型别名。 |
MyBatis支持以下的JDBC类型:
BIT |
FLOAT |
CHAR |
TIMESTAMP |
OTHER |
UNDEFINED |
TINYINT |
REAL |
VARCHAR |
BINARY |
BLOB |
NVARCHAR |
SMALLINT |
DOUBLE |
LONGVARCHAR |
VARBINARY |
CLOB |
NCHAR |
INTEGER |
NUMERIC |
DATE |
LONGVARBINARY |
BOOLEAN |
NCLOB |
BIGINT |
DECIMAL |
TIME |
NULL |
CURSOR |
|
<constructor>
<idArg column="id" javaType="int"/>
<arg column=”username” javaType=”String”/>
</constructor>
当属性与DTO,或者与您本身的域模型一块儿工做的时候,许多场合要用到不变类。一般,包含引用,或者查找的数据不多或者数据不会改变的的表,适合映射到不变类中。构造器注入容许您在类实例化后给类设值,这不须要经过public方法。MyBatis一样也支持private属性和JavaBeans的私有属性达到这一点,可是一些用户可能更喜欢使用构造器注入。构造器元素能够作到这点。
考虑下面的构造器:
public class User {
//…
public User(int id, String username) {
//…
}
//…
}
为了将结果注入构造器,MyBatis须要使用它的参数类型来标记构造器。Java没有办法经过参数名称来反射得到。所以当建立constructor 元素,确保参数是按顺序的而且指定了正确的类型。
<constructor>
<idArg column="id" javaType="int"/>
<arg column=”username” javaType=”String”/>
</constructor>
其它的属性与规则与id、result元素的同样。
Attribute |
Description |
column |
数据库的列名或者列标签别名。与传递给resultSet.getString(columnName)的参数名称相同。 |
javaType |
完整java类名或别名(参考上面的内置别名列表)。若是映射到一个JavaBean,那MyBatis 一般会自行检测到。然而,若是映射到一个HashMap,那您应该明确指定javaType 来确保所需行为。 |
jdbcType |
支持的JDBC类型列表中列出的JDBC类型。这个属性只在insert,update 或delete 的时候针对容许空的列有用。JDBC 须要这项,但MyBatis 不须要。若是您直接编写JDBC代码,在容许为空值的状况下须要指定这个类型。 |
typeHandler |
咱们已经在文档中讨论过默认类型处理器。使用这个属性能够重写默认类型处理器。它的值能够是一个TypeHandler实现的完整类名,也能够是一个类型别名。 |
<association property="author" column="blog_author_id" javaType=" Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
</association>
Association元素处理“has-one”(一对一)这种类型关系。好比在咱们的例子中,一个Blog有一个Author。联合映射与其它的结果集映射工做方式差很少,指定property、column、javaType(一般MyBatis会自动识别)、jdbcType(若是须要)、typeHandler。
不一样的地方是您须要告诉MyBatis 如何加载一个联合查询。MyBatis使用两种方式来加载:
·Nested Select:经过执行另外一个返回预期复杂类型的映射SQL语句(即引用外部定义好的SQL语句块)。
·Nested Results:经过嵌套结果映射(nested result mappings)来处理联接结果集(joined results)的重复子集。
首先,让咱们检查一下元素属性。正如您看到的,它不一样于普通只有select和resultMap属性的结果映射。
Attribute |
Description |
property |
映射数据库列的字段或属性。若是JavaBean 的属性与给定的名称匹配,就会使用匹配的名字。不然,MyBatis 将搜索给定名称的字段。两种状况下您均可以使用逗点的属性形式。好比,您能够映射到”username”,也能够映射到更复杂点的”address.street.number”。 |
column |
数据库的列名或者列标签别名。与传递给resultSet.getString(columnName)的参数名称相同。 注意: 在处理组合键时,您可使用column= “{prop1=col1,prop2=col2}”这样的语法,设置多个列名传入到嵌套查询语句。这就会把prop1和prop2设置到目标嵌套选择语句的参数对象中。 |
javaType |
完整java类名或别名(参考上面的内置别名列表)。若是映射到一个JavaBean,那MyBatis 一般会自行检测到。然而,若是映射到一个HashMap,那您应该明确指定javaType 来确保所需行为。 |
jdbcType |
支持的JDBC类型列表中列出的JDBC类型。这个属性只在insert,update 或delete 的时候针对容许空的列有用。JDBC 须要这项,但MyBatis 不须要。若是您直接编写JDBC代码,在容许为空值的状况下须要指定这个类型。 |
typeHandler |
咱们已经在文档中讨论过默认类型处理器。使用这个属性能够重写默认类型处理器。它的值能够是一个TypeHandler实现的完整类名,也能够是一个类型别名。 |
联合嵌套选择(Nested Select for Association)
select |
经过这个属性,经过ID引用另外一个加载复杂类型的映射语句。从指定列属性中返回的值,将做为参数设置给目标select 语句。表格下方将有一个例子。注意:在处理组合键时,您可使用column=”{prop1=col1,prop2=col2}”这样的语法,设置多个列名传入到嵌套语句。这就会把prop1和prop2设置到目标嵌套语句的参数对象中。 |
例如:
咱们使用两个select语句:一个用来加载Blog,另外一个用来加载Author。Blog的resultMap 描述了使用“selectAuthor”语句来加载author的属性。
若是列名和属性名称相匹配的话,全部匹配的属性都会自动加载。
译者注: 上面的例子,首先执行<select id=“selectBlog”>,执行结果存放到<resultMap id=“blogResult”>结果映射中。“blogResult”是一个Blog类型,从<select id=“selectBlog”>查出的数据都会自动赋值给”blogResult”的与列名匹配的属性,这时blog_id,title等就被赋值了。同时“blogResult”还有一个关联属性"Author",执行嵌套查询select=”selectAuthor”后,Author对象的属性id,username,password,email,bio也被赋于数据库匹配的值。
Blog { blog_id; title; Author author { id; username; password; email; bio;
}
} |
虽然这个方法简单,可是对于大数据集或列表查询,就不尽如人意了。这个问题被称为“N+1 选择问题”(N+1 Selects Problem)。归纳地说,N+1选择问题是这样产生的:
·您执行单条SQL语句去获取一个列表的记录( “+1”)。
·对列表中的每一条记录,再执行一个联合select 语句来加载每条记录更加详细的信息(“N”)。
这个问题会致使成千上万的SQL语句的执行,所以并不是老是可取的。
上面的例子,MyBatis可使用延迟加载这些查询,所以这些查询立马可节省开销。然而,若是您加载一个列表后当即迭代访问嵌套的数据,这将会调用全部的延迟加载,所以性能会变得很是糟糕。
鉴于此,这有另一种方式。
联合嵌套结果集(Nested Results for Association)
resultMap |
一个能够映射联合嵌套结果集到一个适合的对象视图上的ResultMap 。这是一个替代的方式去调用另外一个select 语句。它容许您去联合多个表到一个结果集里。这样的结果集可能包括冗余的、重复的须要分解和正确映射到一个嵌套对象视图的数据组。简言之,MyBatis 让您把结果映射‘连接’到一块儿,用来处理嵌套结果。举个例子会更好理解,例子在表格下方。 |
您已经在上面看到了一个很是复杂的嵌套联合的例子,接下的演示的例子会更简单一些。咱们把Blog和Author表联接起来查询,而不是执行分开的查询语句:
在上面的例子中,您会看到Blog的做者(“author”)联合一个“authorResult”结果映射来加载Author实例。
重点提示:id元素在嵌套结果映射中扮演了很是重要的角色,您应该老是指定一个或多个属性来惟一标识这个结果集。事实上,若是您没有那样作,MyBatis也会工做,可是会致使严重性能开销。选择尽可能少的属性来惟一标识结果,而使用主键是最明显的选择(即便是复合主键)。
上面的例子使用一个扩展的resultMap 元素来联合映射。这可以使Author结果映射可重复使用。而后,若是您不须要重用它,您能够直接嵌套这个联合结果映射。下面例子就是使用这样的方式:
collection元素的做用差很少和association元素的做用同样。事实上,它们很是类似,以致于再对类似点进行描述会显得冗余,所以咱们只关注它们的不一样点。
继续咱们上面的例子,一个Blog只有一个Author。但一个Blog有许多帖子(文章)。在Blog类中,会像下面这样定义相应属性:
private List<Post> posts;
映射一个嵌套结果集到一个列表,咱们使用collection元素。就像association 元素那样,咱们使用嵌套查询,或者从链接中嵌套结果集。
集合嵌套选择(Nested Select for Collection)
首先咱们使用嵌套选择来加载Blog的文章。
一看上去这有许多东西须要注意,但大部分看起与咱们在association元素中学过的类似。首先,您会注意到咱们使用了collection元素,而后会注意到一个新的属性“ofType”。这个元素是用来区别JavaBean属性(或者字段)类型和集合所包括的类型。所以您会读到下面这段代码。
<collection property="posts" javaType=”ArrayList” column="blog_id"
ofType="Post" select=”selectPostsForBlog”/>
è理解为:“一个名为posts,类型为Post的ArrayList集合(A collection of posts in an ArrayList of type Post)” 。
javaType属性不是必须的,一般MyBatis 会自动识别,因此您一般能够简略地写成:
<collection property="posts" column="blog_id" ofType="Post"
select=”selectPostsForBlog”/>
集合的嵌套结果集(Nested Results for Collection)
这时候,您可能已经猜出嵌套结果集是怎样工做的了,由于它与association很是类似,只不过多了一个属性“ofType”。
让咱们看下这个SQL:
再次强调一下,id 元素是很是重要的。若是您忘了或者不知道id 元素的做用,请先读一下上面association一节。
若是但愿结果映射有更好的可重用性,您可使用下面的方式:
èNote:在您的映射中没有深度、宽度、联合和集合数目的限制。但应该谨记,在进行映射的时候也要考虑性能的因素。应用程序的单元测试和性能测试帮助您发现最好的方式可能要花很长时间。但幸运的是,MyBatis容许您之后能够修改您的想法,这时只须要修改少许代码就好了。
关于高级联合和集合映射是一个比较深刻的课题,文档只能帮您了解到这里,多作一些实践,一切将很快变得容易理解。
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
</discriminator>
有时候一条数据库查询可能会返回包括各类不一样的数据类型的结果集。Discriminator(识别器)元素被设计来处理这种状况,以及其它像类继承层次状况。识别器很是好理解,它就像java里的switch语句。
Discriminator定义要指定column和javaType属性。列是MyBatis将要取出进行比较的值,javaType用来肯定适当的测试是否正确运行(虽然String在大部分状况下均可以工做),例:
在这个例子中,MyBatis将会从结果集中取出每条记录,而后比较它的vehicle type的值。若是匹配任何discriminator中的case,它将使用由case指定的resultMap。这是排它性的,换句话说,其它的case的resultMap将会被忽略(除非使用咱们下面说到的extended)。若是没有匹配到任何case,MyBatis只是简单的使用定义在discriminator块外面的resultMap。因此,若是carResult像下面这样定义:
<resultMap id="carResult" type="Car">
<result property=”doorCount” column="door_count" />
</resultMap>
那么,只有doorCount属性会被加载。这样作是为了与识别器cases群组彻底独立开来,哪怕它与上一层的resultMap一点关系都没有。在刚才的例子里咱们固然知道cars和vehicles的关系,a Car is-a Vehicle。所以,咱们也要把其它属性加载进来。咱们要稍稍改动一下resultMap:
<resultMap id="carResult" type="Car"extends=”vehicleResult”>
<result property=”doorCount” column="door_count" />
</resultMap>
如今,vehicleResult和carResult的全部属性都会被加载。
可能有人会认为这样扩展映射定义有一点单调了,因此还有一种可选的更加简单明了的映射风格语法。例如:
MyBatis包含一个强大的、可配置、可定制的查询缓存机制。MyBatis 3 的缓存实现有了许多改进,使它更强大更容易配置。默认的状况,缓存是没有开启,除了会话缓存之外,它能够提升性能,且能解决循环依赖。开启二级缓存,您只须要在SQL映射文件中加入简单的一行:
<cache/>
这句简单的语句做用以下:
·全部映射文件里的select语句的结果都会被缓存。
·全部映射文件里的insert、update和delete语句执行都会清空缓存。
·缓存使用最近最少使用算法(LRU)来回收。
·缓存不会被设定的时间所清空。
·每一个缓存能够存储1024 个列表或对象的引用(无论查询方法返回的是什么)。
·缓存将做为“读/写”缓存,意味着检索的对象不是共享的且能够被调用者安全地修改,而不会被其它调用者或者线程干扰。
全部这些特性均可以经过cache元素进行修改。例如:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这种高级的配置建立一个每60秒刷新一次的FIFO 缓存,存储512个结果对象或列表的引用,而且返回的对象是只读的。所以在不用的线程里的调用者修改它们可能会引用冲突。
可用的回收算法以下:
·LRU–最近最少使用:移出最近最长时间内都没有被使用的对象。
·FIFO–先进先出:移除最早进入缓存的对象。
·SOFT–软引用: 基于垃圾回收机制和软引用规则来移除对象(空间内存不足时才进行回收)。
·WEAK–弱引用:基于垃圾回收机制和弱引用规则(垃圾回收器扫描到时即进行回收)。
默认使用LRU。
flushInterval:设置任何正整数,表明一个以毫秒为单位的合理时间。默认是没有设置,所以没有刷新间隔时间被使用,在语句每次调用时才进行刷新。
Size:属性能够设置为一个正整数,您须要留意您要缓存对象的大小和环境中可用的内存空间。默认是1024。
readOnly:属性能够被设置为true 或false。只读缓存将对全部调用者返回同一个实例。所以这些对象都不能被修改,这能够极大的提升性能。可写的缓存将经过序列化来返回一个缓存对象的拷贝。这会比较慢,可是比较安全。因此默认值是false。
使用自定义缓存
除了上面已经定义好的缓存方式,您可以经过您本身的缓存实现来彻底重写缓存行为,或者经过建立第三方缓存解决方案的适配器。
<cache type=”com.domain.something.MyCustomCache”/>
这个例子演示了若是自定义缓存实现。由type指定的类必须实现org.mybatis.cache.Cache接口。这个接口是MyBatis框架比较复杂的接口之一,先给个示例:
要配置您的缓存,简单地添加一个公共的JavaBeans 属性到您的缓存实现中,而后经过cache 元素设置属性进行传递,下面示例,将在您的缓存实现上调用一个setCacheFile(String file)方法。
<cache type=”com.domain.something.MyCustomCache”>
<property name=”cacheFile” value=”/tmp/my-custom-cache.tmp”/>
</cache>
您可使用全部简单的JavaBeans属性,MyBatis会自动进行转换。
须要牢记的是一个缓存配置和缓存实例都绑定到一个SQL Map 文件命名空间。所以,全部的这个相同命名空间的语句也都和这个缓存绑定。语句能够修改如何与这个缓存相匹配,或者使用两个简单的属性来彻底排除它们本身。默认状况下,语句像下面这样来配置:
<select ... flushCache=”false” useCache=”true”/>
<insert ... flushCache=”true”/>
<update ... flushCache=”true”/>
<delete ... flushCache=”true”/>
由于有默认值,因此您不须要使用这种方式明确地配置这些语句。若是您想改变默认的动做,只须要设置flushCache和useCache 属性便可。举个例子来讲,在许多的场合下您可能排除缓存中某些特定的select语句。或者您想用select语句清空缓存。一样的,您也可能有一些update 语句在执行的时候不须要清空缓存。
回想上一节,咱们仅仅只是讨论在某一个命名空间里使用或者刷新缓存。但有可能您想要在不一样的命名空间里共享同一个缓存配置或者实例。在这种状况下,您就可使用cache-ref 元素来引用另一个缓存。
<cache-ref namespace=”com.someone.application.data.SomeMapper”/>
接下来看下一个完整的sqlmap能够运行的ibatis文件: