iBATIS第一部分基础
一、什么是iBATIS?
iBATIS是一个简单但完整的框架,它使您轻松地映射你的对象到您的SQL语句或存储过程。iBATIS的框架目标是获得80%的数据访
问功能只使用20%的代码。
开发人员常常在一个应用程序的对象之间建立映射,iBATIS是一种data mapper即一个映射层,在对象和数据库间传递数据,并保
持两者与映射层本身相独立。
译注:在C#中通常在类的属性(Property)与表的列间进行映射。
iBATIS则与之不同,它不是直接在类与数据表或字段与列之间进行关联,而是把SQL语句的参数(parameter)和返回结果
(result)映射至类。
iBATIS是处于类和数据表之间的一个中间层,这使得它在类和数据表之间进行映射时更加灵活,而不需要数据库模型或对象模型
(object model)的任何修改。我们所说的中间层实际上就是SQL,它使得iBATIS能够更好地分离数据库和对象模型的设计,这样
就相对减少了两者间的耦合。
这种方式的优势在于SQL语句给开发人员带来了很大的灵活性。我们可以轻松地操作数据使之与对象模型匹配而无需修改后台的数
据库设计。此外,开发人员可以使用内置的数据库函数或存储过程来返回多个不同的表或结果,SQL的强大能力变得信手拈来。
二,它是怎样工作的?
2.1
你的程序平台已经提供了一个访问数据库的可用类库,但是无论是通过SQL语句或存储过程,开发人员总是会发现有几件事用常用
的ADO.NET仍很难做好,其中包括:
1,从程序代码中分离SQL代码;
2,传递输入参数到类库并且提取输出;
3,从务逻辑层分离数据访问类;
4,缓存经常使用的数据,直到它改变;
5,管理事物与线程;
iBATIS的DataMapper解决了这些问题-以及更多-通过使用XML文档在一个普通的对象与SQL语句或存储过程间建立映射。这个“plain-old object”可以是一个IDictionary或属性对象。这个"plain-old object" 可以是一个IDictionary或property对象.
提示
这个对象不需要是某个特定层级对象的一部分或者实现一个特殊的接口。(Which is why we call them "plain-old" objects.)
2.2 iBATIS的DataMapper工作流程
提供一个参数,无论是作为对象或本机类型.这个参数可以用来设定您的SQL语句或存储过程运行时的值,如果运行时不需要值,参数可以省略;
通过传递给XML中的statement or procedure 的参数和名称执行映射。这一步神奇的地方发生了。该框架将准备SQL语句或存储过程,用你的参数设置运行时的值,执行该过程或语句,并返回结果;
在一个更新的情况下,受影响的行数返回。在一个查询的情况下,一个单一的对象,或对象的集合返回。像参数,结果对象或对象的集合,可以是plain-old对象或本地类型。
三,
1,QueryForObject()方法
QueryForObject()方法用于从数据库中获取单行数据,并将其转换为C#对象,它共有4种签名:
首先可以把它们分为两种:泛型和非泛型。每一种又分别有两个版本,第一个版本更为常用,它会使用默认构造函数创建一个新的对象返回(否则会在运行时抛出异常);第二个版本接受一个对象将其作为返回值——在执行映射语句后,会设置它的相应属性,而不是创建新的对象。
需要注意的是,调用QueryForObject()方法时,如果查询结果多于一行,那么iBATIS只会接受第一行记录,其它的记录则忽略不计。
2,QueryForList() 方法
IList QueryForList(string statementName, object parameterObject);
void QueryForList(string statementName, object parameterObject, IList resultObject);
IList QueryForList(string statementName, object parameterObject, int skipResults, int maxResults);
IList<T> QueryForList<T>(string statementName, object parameterObject);
void QueryForList<T>(string statementName, object parameterObject, IList<T> resultObject);
IList<T> QueryForList<T>(string statementName, object parameterObject, int skipResults, int maxResults);
QueryFoList()方法用于数据库中获取一行或多行数据,
3,QueryForMap() 方法
QueryForMap()方法从数据库获取一行或多行记录,将结果转换为C#对象,然后放在一个IDictionary(而不是IList)中返回。
四, 自动结果映射
因为它本来就是将SQL语句映射为对象,而不是从表到对象。从字面上来看,对单表的<select>和多表的<select>进行映射没什么不同。
3.3.4.1 id
ID提供了声明的名字必须在<SqlMap>中是唯一的 。
3.3.4.2. parameterMap
一个parameterMap定义了一个值的有序列表匹配的一个标准的“?”占位符,参数化查询语句
<parameterMap id="insert-product-param" class="Product">
<parameter property="id"/>
<parameter property="description"/>
</parameterMap>
<statement id="insertProduct" parameterMap="insert-product-param">
insert into PRODUCT (PRD_ID, PRD_DESCRIPTION) values (?,?);
</statement>
3.3.4.3. parameterClass
如果一个parameterMap属性没有指定,您可以指定parameterClass替代使用内联参数(见第3.4.3)。该parameterClass属性值可
以是一个类型别名或一类的完全限定名,例如3.10显示了一个声明使用一个完全限定的类名做别名
<!-- fully qualified classname -->
<statement id="statementName" parameterClass="Examples.Domain.Product, Examples.Domain">
insert into PRODUCT values (#id#, #description#, #price#)
</statement>
<!-- typeAlias (defined elsewhere) -->
<statement id="statementName" parameterClass="Product">
insert into PRODUCT values (#id#, #description#, #price#)
</statement>
3.3.4.4. resultMap
resultMap可以让你控制数据如何从查询结果中提取,以及如何将列映射到对象的属性。例如3.11显示了<resultMap>元素和相应
的<statement>元素。
<resultMap id="select-product-result" class="product">
<result property="id" column="PRD_ID"/>
<result property="description" column="PRD_DESCRIPTION"/>
</resultMap>
<statement id="selectProduct" resultMap="select-product-result">
select * from PRODUCT
</statement>
在示例3.11,在SQL查询的结果将被映射到一个 Product class 实例,这个<resultMap>说PRD_ID构造id属性,并从
D_DESCRIPTION构造description属性。
3.3.4.5. resultClass
如果一个resultMap没有指定,您可以指定resultClass代替。该resultClass属性的值可以是一个类型别名或一类的完全限定类名
。指定的类会自动映射到结果列基于元数据的结果。下面的例子显示了<statement>元素的resultClass属性。
<statement id="SelectPerson" parameterClass="int" resultClass="Person">
SELECT
PER_ID as Id,
PER_FIRST_NAME as FirstName,
PER_LAST_NAME as LastName,
PER_BIRTH_DATE as BirthDate,
PER_WEIGHT_KG as WeightInKilograms,
PER_HEIGHT_M as HeightInMeters
FROM PERSON
WHERE PER_ID = #value#
</statement>
在示例3.12 Person类有包括属性:Id, FirstName, LastName, BirthDate, WeightInKilograms, and HeightInMeters。这些都
对应SQL语句中使用AS 指的别名。执行时,person对象被实例化生成,通过匹配查询列的对象属性名。
使用SQL别名映射列到属性定义一个<resultMap>元素,但也有局限性。没有方法来指定输出列(如果需要)类型,有没有办法自
动加载相关数据数据,如复杂的属性,而且访问元数据有轻微的性能影响。
3.3.4.6. listClass
除了提供能够返回一个IList的对象,DataMapper也支持强类型的自定义集合:实现System.Collections.CollectionBase抽象类
的一个类。下面是可以被DataMapper用的一个CollectionBase类,。
<statement id="GetAllAccounts"
listClass="AccountCollection"
resultClass="Account">
select
Account_ID as Id,
Account_FirstName as FirstName,
Account_LastName as LastName,
Account_Email as EmailAddress
from Accounts
order by Account_LastName, Account_FirstName
</statement>
3.3.4.7. cacheModel
如果你想缓存查询结果,您可以指定缓存模型做为<statement>元素的一部分。例如3.15显示了<cacheModel>元素和相应的
<statement>
<cacheModel id="product-cache" implementation="LRU">
<flushInterval hours="24"/>
<flushOnExecute statement="insertProduct"/>
<flushOnExecute statement="updateProduct"/>
<flushOnExecute statement="deleteProduct"/>
<property name="size" value="1000" />
</cacheModel>
<statement id="selectProductList" parameterClass="int" cacheModel="product-cache">
select * from PRODUCT where PRD_CAT_ID = #value#
</statement>
在示例3.15,为products定义了一个缓存,它使用一个引用类型和LRU算法,每24小时刷新或更新或执行相关语句。有关更多有关
缓存模型,第3.8节。
3.3.4.8. extends
When writing Sql, you often encounter duplicate fragments of SQL. iBATIS offers a simple yet powerful attribute
to reuse them.
<select id="GetAllAccounts"
resultMap="indexed-account-result">
select
Account_ID,
Account_FirstName,
Account_LastName,
Account_Email
from Accounts
</select>
<select id="GetAllAccountsOrderByName"
extends="GetAllAccounts"
resultMap="indexed-account-result">
order by Account_FirstName
</select>
3.8. Cache Models
数据库中的一些值改变的比别的慢。为了提高性能,许多开发人员喜欢缓存那些经常使用的数据,以避免不必要的往返数据库,iBATIS的提供了自己的缓存系统,您可以通过<cacheModel>元素配置。
从查询结果映射的声明可以通过在声明标记中指定cacheModel参数进行缓存,缓存模型是在您的DataMapper配置文件中定义的配置缓存。缓存模型用下面的cacheModel元素配置使用:
Example 3.51. Configuation a cache using the Cache Model element
<cacheModel id="product-cache" implementation="LRU" readOnly="true" serialize="false">
<flushInterval hours="24"/>
<flushOnExecute statement="insertProduct"/>
<flushOnExecute statement="updateProduct"/>
<flushOnExecute statement="deleteProduct"/>
<property name="CacheSize" value="100"/>
</cacheModel>
以上的缓存模型将创建一个命名为“product-cache”的实例,使用Least Recently Used(LRU)的执行。该类型的属性值可以是一个完全限定类名,或者是其中所包含的实现的别名。根据缓存模型中指定的flush元素,该缓存每24小时都将被刷新,只能有一个flushInterval元素,它可以被设置使用时,分,秒和毫秒,另外当insertProduct, updateProduct, or deleteProduct 的映射声明被执行时,缓存将被刷新。在一个缓存中可以有任意数量的“flush on execute”元素,一些缓存的实现可能需要额外的属性,如上面的CacheSize。就LTU缓存来说,size就决定了存储在缓存中的实体数量。一旦缓存模型被配置,您就可以指定被一个映射声明使用的缓存声明,例如:
Example 3.52. Specifying a Cache Model from a Mapped Statement
<statement id="getProductList" cacheModel="product-cache">
select * from PRODUCT where PRD_CAT_ID = #value#
</statement>
3.8.1. Read-Only vs. Read/Write
该框架同时支持只读和读/写缓存,只读缓存是所有用户共享,因此性能更好。然而,从只读缓存读取的对象不能修改。然而,一个新的对象应该是从数据库(或读/写缓存)中读取出来用于更新。另一方面,如果用于对象的检索和修改,则读/写缓存则是必须的。要使用只读缓存,设置只读=“真”的缓存模型元素。要使用读/写缓存,设置只读=“假”。默认为只读(真)。
3.8.2. Serializable Read/Write Caches
如您同意,缓存每个会话对整个应用程序的性能没有益处。提高应用程序性能的一种读/写缓存类型是序列化读/写缓存。这种缓存将返回每个会话缓存对象的副本,因此,每个会话都可以安全的修改返回的对象。认识到这一点,则你会期望相同的对象从缓存中返回,否则的话则得一个新 的。还有每一个存储在序列化缓存中的对象必须被序列化。这意味着你很难同时使用延迟加载特性与序列化缓存,因为lazy proxies不能被序列化。使用什么样的of caching, lazy loading and table joining组合的简单方式就是试一下。使用序列化的缓存设置readOnly="false" and serialize="true".默认情况下缓存模型只读和非序列,只读缓存不能被序列化。
3.8.3. Cache Implementation
该缓存模型使用可插拔框架来支持不同类型的缓存。缓存的选择在cacheModel元素的"implementation"属性中指定。指定的类名必须实现一个ICacheController接口或下面讨论的三个别名中的一个。其他配置参数可以通过cacheModel中的属性元素传递给implementation。目前,有3种实现包含在。NET中。这些如下:
3.8.4. "MEMORY"
MEMORY cache使用reference类型来管理cache的行为。也就是说,垃圾收集器可以决定谁可以停留在缓存中。MEMORY cache是应用程序一个很好的选择(没有对象重用的识别模式。与内存较少的应用程序)
MEMORY implementation配置如下:
<cacheModel id="product-cache" implementation="MEMORY" >
<flushInterval hours="24"/>
<flushOnExecute statement="insertProduct"/>
<flushOnExecute statement="updateProduct"/>
<flushOnExecute statement="deleteProduct"/>
<property name="Type" value="WEAK"/>
</cacheModel>
只有一个属性被MEMORY cache识别。此属性,命名为' 'reference-type' '必须设置为STRONG, SOFT, or WEAK.。下表描述了a MEMORY cache中不同的引用类型;
Type |
Description |
WEAK (default) |
这个引用类型,在大多数情况下是最好的选择,如果引用类型未指定是默认的,它会增的结果的性能,如果结果目前未使用则释放内存分配给其他对象使用 |
SOFT (currently Java only) |
This reference type will reduce the likelihood of running out of memory in case the results are not currently in use and the memory is needed for other objects. However, this is not the most aggressive reference type in that regard and memory still might be allocated and made unavailable for more important objects. |
STRONG |
这种类型可以保证结果留在内存中,直到缓存明确刷新这个注意是因以下结果 1) very small, 2) absolutely static, and 3) used very often. 它的优点是对特定的查询性能会非常好这个缺点是,缺点是如果这些结果所使用的内存是必要的,那么就不会被释放让其他对象使用 |
3.8.5. "LRU"
在LRU缓存实现中使用最近使用的算法来确定一个对象是如何自动从缓存中删除的,当Cache对已满,这是最近最少使用的将是从缓存中删除对象。这样,如果有一个对象经常被使用则它将留在缓存中否则被删除。在LRU Cache对于使用模式(某个对象在一长段时间内经常被一个或多个用户使用)将是一个很好的选择。
Example 3.54. Configuring a LRU type cache
<cacheModel id="product-cache" implementation="LRU" >
<flushInterval hours="24"/>
<flushOnExecute statement="insertProduct"/>
<flushOnExecute statement="updateProduct"/>
<flushOnExecute statement="deleteProduct"/>
<property name="CacheSize" value="100"/>
</cacheModel>
一个属性可被LRU cache识别。CacheSize,必须设置为一个整数值,代表在缓存中一次存储对象的最大数。一个重要的是要记住的是,一个对象可是单一的String对象或任何一个ArrayList的对象。因此,不要存储在缓存太多,以防内存不足!
3.8.6. "FIFO"
FIFO的缓存实现使用先进先出算法来确定如何从缓存中自动删除对象。当Cache满时,最古老的对象将被从缓存中删除。 FIFO缓存有利于使用这种模式(一个特定的查询接连几次被引用,但后来一段时间可能不用)。
<cacheModel id="product-cache" implementation="FIFO" >
<flushInterval hours="24"/>
<flushOnExecute statement="insertProduct"/>
<flushOnExecute statement="updateProduct"/>
<flushOnExecute statement="deleteProduct"/>
<property name="CacheSize" value="100"/>
</cacheModel>
一个属性可被LRU cache识别。CacheSize,必须设置为一个整数值,代表在缓存中一次存储对象的最大数。一个重要的是要记住的是,一个对象可是单一的String对象或任何一个ArrayList的对象。因此,不要存储在缓存太多,以防内存不足!
3.9. Dynamic SQL
直接用ADO有个很常见的问题就是动态SQL,通常当SQL语句改变时它很难工作,不仅是参数值而且指被包含的那些参数和列。
典型的解决方案通常是大量的if - else语句和可怕的字符串串联。期望的结果往往是通过示例查询。
在iBATIS的DataMapper API提供了一个比较优雅的解决方案,可应用于任何映射的声明元素。下面是一个简单的例子:
Example 3.56. A simple dynamic select sttatement, with two possible outcomes
<select id="dynamicGetAccountList" cacheModel="account-cache" parameterClass="Account" resultMap="account-result"
>
select * from ACCOUNT
<isGreaterThan prepend="and" property="Id" compareValue="0">
where ACC_ID = #Id#
</isGreaterThan>
order by ACC_LAST_NAME
</select>
在上面的例子,有两个可能的声明可能被创造根据参数对象的Id属性的状态。如果ID参数大于0,那么将创建声明如下:
select * from ACCOUNT where ACC_ID = ?
或者如果ID参数为0或更少,该语句将如下所示。
select * from ACCOUNT
这个可能不会立即显现效用,直到一个更复杂的情况是遇到。例如,下面是一个较为复杂的例子。
Example 3.57. A complex dynamic select statement, with 16 possible outcomes
<select id="dynamicGetAccountList" parameterClass="Account" resultMap="account-result" >
select * from ACCOUNT
<dynamic prepend="WHERE">
<isNotNull prepend="AND" property="FirstName">
( ACC_FIRST_NAME = #FirstName#
<isNotNull prepend="OR" property="LastName">
ACC_LAST_NAME = #LastName#
</isNotNull>
)
</isNotNull>
<isNotNull prepend="AND" property="EmailAddress">
ACC_EMAIL like #EmailAddress#
</isNotNull>
<isGreaterThan prepend="AND" property="Id" compareValue="0">
ACC_ID = #Id#
</isGreaterThan>
</dynamic>
order by ACC_LAST_NAME
</select>
根据情况,可能有多达16种不同的SQL查询语句由上述动态语句动态生成。
<statement id="someName" parameterClass="Account" resultMap="account-result" >
select * from ACCOUNT
<dynamic prepend="where">
<isGreaterThan prepend="and" property="id" compareValue="0">
ACC_ID = #id#
</isGreaterThan>
<isNotNull prepend="and" property="lastName">
ACC_LAST_NAME = #lastName#
</isNotNull>
</dynamic>
order by ACC_LAST_NAME
</statement>
在上述声明中,由<dynamic>元素划定的SQL中的一部分是动态的,动态元素是可选的,并提供了一种方法来管理这种情况像
(where)不应被包括除非包含的条件追加到声明。声明部分可以包含任意数量的条件元素(见下文),用以确定包含的SQL代码
是否将被包括在声明中。所有的条件元素都基于似递进查询语句的参数状态。无论是动态的元素还是条件元素有“prepend”属性
prepend属性是代码的一部分可以被父元素的 prepend属性重写。在上面的"where"将覆盖第一个为true的条件的prepend。这对
确保SQL语句生成正确是必要的,例如,在第一个条件为true的条件下,and是没有必要的,否则它会破坏声明。以下各节描述的
各种元素,包括二元条件句,一元条件句种,以及迭代。
3.9.1. Binary Conditional Elements
二元条件元素比较一个属性质跟一个静态值或另一个属性值。如果结果是真,主题内容被包在SQL查询中。
prepend可重载SQL的一部分可以被加在声明的前面
property:将要被比较的属性值;
compareProperty:将要被比较的另一个属性值;
compareValue:将要被比较的值;
le 3.7. Binary conditional attributes
Element |
Description |
<isEqual> |
Checks the equality of a property and a value, or another property. Example Usage: <isEqual prepend="AND" property="status" compareValue="Y"> MARRIED = ‘TRUE' </isEqual> |
<isNotEqual> |
Checks the inequality of a property and a value, or another property. Example Usage: <isNotEqual prepend="AND" property="status" compareValue="N"> MARRIED = ‘FALSE' </isNotEqual> |
<isGreaterThan> |
Checks if a property is greater than a value or another property. Example Usage: <isGreaterThan prepend="AND" property="age" compareValue="18"> ADOLESCENT = ‘FALSE' </isGreaterThan> |
<isGreaterEqual> |
Checks if a property is greater than or equal to a value or another property. Example Usage: <isGreaterEqual prepend="AND" property="shoeSize" compareValue="12"> BIGFOOT = ‘TRUE' </isGreaterEqual> |
<isLessEqual> |
Checks if a property is less than or equal to a value or another property. Example Usage: <isLessEqual prepend="AND" property="age" compareValue="18"> ADOLESCENT = ‘TRUE' </isLessEqual> |
3.9.2. Unary Conditional Elements
一元条件元素检查特定条件的属性状态;
prepend – the overridable SQL part that will be prepended to the statement (optional) |
property – the property to be checked (required) |
3.9.3. Parameter Present Elements
这个元素检查参数对象是否存在;
3.9.3.1. Parameter Present Attributes:
prepend – the overridable SQL part that will be prepended to the statement (optional)
Table 3.9. Testing to see if a parameter is present
Element |
Description |
<isParameterPresent> |
Checks to see if the parameter object is present (not null). <isParameterPresent prepend="AND"> EMPLOYEE_TYPE = #empType# </isParameterPresent> |
<isNotParameterPresent> |
Checks to see if the parameter object is not present (null). Example Usage: <isNotParameterPresent prepend="AND"> EMPLOYEE_TYPE = ‘DEFAULT' </isNotParameterPresent> |
3.9.4. Iterate Element
这个标签将遍历集合与重复列表中的每个项目的主体内容
3.9.4.1. Iterate Attributes:
prepend – the overridable SQL part that will be prepended to the statement (optional) |
property – a property of type IList that is to be iterated over (required) |
open – the string with which to open the entire block of iterations, useful for brackets (optional) |
close – the string with which to close the entire block of iterations, useful for brackets (optional) |
conjunction – the string to be applied in between each iteration, useful for AND and OR (optional) |
Table 3.10. Creating a list of conditional clauses
Element |
Description |
<iterate> |
Iterates over a property that is of type IList Example Usage: <iterate prepend="AND" property="UserNameList" open="(" close=")" conjunction="OR"> username=#UserNameList[]# </iterate> Note:包含这个[]是很重要的当使用迭代元素时,这个括号让语法分析器识别这个对 象做为list对象而不是做为字符串输出; |
3.9.5. Simple Dynamic SQL Elements
尽管完整的动态映射声明API上已讲述,但有时你只需要一个简单的,小块的SQL是动态的。为此,SQL语句和声明可以包含简单的动态SQL元素,以帮助执行按项排序,动态查询列或几乎任何SQL语句的一部分。这个概念很像内联参数映射,但使用了稍微不同的语法。看看下面的例子:
Example 3.59. A dynamic element that changes the collating order
<statement id="getProduct" resultMap="get-product-result">
select * from PRODUCT order by $preferredOrder$
</statement>
在上面的例子中preferredOrder动态元素将被由参数对象的preferredOrder属性值替换(就像一个参数)。所不同的是,这是从根本上改变了SQL语句本身,这比简单设置一个值要严谨。引进了安全,性能和稳定性的风险,要注意做大量的重复检查,以确保简单动态SQL元素被适当使用,此外,心系你的设计,因为对数据库的细节可能会侵犯您的业务对象模型,
简单的动态元素可以包含在<statements>并派得上用场当有需要修改SQL语句本身
<statement id="getProduct" resultMap="get-product-result">
SELECT * FROM PRODUCT
<dynamic prepend="WHERE">
<isNotEmpty property="Description">
PRD_DESCRIPTION $operator$ #Description#
</isNotEmpty>
</dynamic>
</statement>