第一部分 概述
(一)目标和初衷
1. iBATIS的目标是:用少量的代码获得大量的数据访问功能
2. 初衷是让程序员将如下过程做的更好更简单:
Separating SQL code from programming code
将SQL代码从程序代码中分离
Passing input parameters to the library classes and extracting the output
对类库传递输入参数来提取输出结果
Separating data access classes from business logic classes
将数据访问从业务逻辑类中分离
Caching often-used data until it changes
将常用数据进行缓存,除非其发生变化
Managing transactions and threading
对数据交换和线程进行管理
(二)iBATIS映射工作流的描述
1.图示:
iBATIS DataMapper workflow
2.映射工作流的具体描述:
1)提供所需要的参数,可以是对象也可以是值类型。此参数用于在SQL语句或存储过程中设定一些运行时的值。也可在不必要的情况下省略此定义。
2)通过传递参数和定义在XML描述文件中的过程或声明的名称,来执行映射。框架会准备好SQL声明语句段, 或存储过程,用传入的参数设定所有运行时必需的值,执行后返回结果。
3)如果是update语句,已更改的行数会被返回,如果是一个查询语句,返回一个对象或一个对象集合。
(三)示例用法
1.XML描述文件部分:
<insert id="InsertLineItem" parameterClass="LineItem">
INSERT INTO [LinesItem]
(Order_Id, LineItem_LineNum, Item_Id, LineItem_Quantity, LineItem_UnitPrice)
VALUES
(#Order.Id#, #LineNumber#, #Item.Id#, #Quantity#, #Item.ListPrice#)
<selectKey type="post" resultClass="int" property="Id" >
select @@IDENTITY as value
</selectKey>
</insert>
2.C#语句:
Mapper.Instance().Insert("InsertLineItem",lineItem);
ps1:若数据库可自动生成主键,通过如下语句执行插入语句,则主键值可被返回
int myKey=Mapper.Instance().Insert("InsertLineItem",lineItem);
ps2:描述文件中,<selectKey>部分规定了从数据库中返回自动生成的主键。
(四)如何确定是否应该使用iBATIS
1.iBATIS的角色是在一个对象的属性和数据库查询的列(包括存储过程)之间建立映射。如果项目是基于业务对象(包括Maps或IDictionary),则iBATIS是不错的选择。如果项目是分层架构的,以便业务层于用户接口层相互独立,iBATIS能发挥的更好,
2.使用OR/M更好的情况:
Have complete control over your database implementation
可以对数据库实现完全掌控
Do not have a Database Administrator or SQL guru on the team
队伍中没有DBA或者SQL大牛
Need to model the problem domain outside the database as an object graph.
需要在数据库外部,将问题域构建成图表对象形式
使用iBATIS更好的情况:
You do not have complete control over the database implementation, or want to continue to
access a legacy database as it is being refactored.
不能对数据库实现完全掌控,或希望在一个数据库被重构阶段依然能对其访问
You have database administrators or SQL gurus on the team.
队伍中有DBA或者SQL大牛
The database is being used to model the problem domain, and the application's primary role is
to help the client use the database model.
数据库本身用来对问题域建模,而项目的主要角色是帮助客户使用数据库模型
第二部分 数据映射工作方式
(一)概述
数据映射定义在一个描述文件中。通过使用iBATIS提供的常规服务,XML描述文件会呈现在客户端对象中。为了访问你的数据地图,应用程序会向客户端对象发出请求并传递所需的statement名称。
(二)XML描述文件内容(也称Data Map definition file)
EX3.1 A Simple Data Map
<?xml version="1.0" encoding="UTF-8" ?>
<sqlMap namespace="LineItem"
xmlns="http://ibatis.apache.org/mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
<!--Type aliases allow you to use a shorter name for long fully qualified class names.-->
<alias>
<typeAlias alias="LineItem" type="NPetshop.Domain.Billing.LineItem, NPetshop.Domain" />
</alias>
<statements>
<insert id="InsertLineItem" parameterClass="LineItem">
INSERT INTO [LinesItem]
(Order_Id, LineItem_LineNum, Item_Id, LineItem_Quantity, LineItem_UnitPrice)
VALUES
(#Order.Id#, #LineNumber#, #Item.Id#, #Quantity#, #Item.ListPrice#)
</insert>
</statements>
</sqlMap>
此映射从LineItem实例中获取参数传入statements中,因此添加值得sql语句从项目代码中分离出来。而且我们可以直接对应一个库方法:
Mapper.Instance().Insert(“InsertLineItem”,lineItem);
EX3.2 A Data Map definition file with some bells and whistles
<?xml version="1.0" encoding="UTF-8" ?>
<sqlMap namespace="Product" //此命名空间和下文中的class有何关联或区别?
xmlns="http://ibatis.apache.org/mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
//给type对应的类定义别名
<alias>
<typeAlias alias="Product" type="Example.Domain.Product, Example.Domain" />
</alias>
//设定缓存参数
<cacheModels>
<cacheModel id="productCache" implementation="LRU"> //???
<flushInterval hours="24"/> //维持时间上限为24小时
<property name="CacheSize" value="1000" /> //保存记录数上限为1000条
</cacheModel>
</cacheModels>
//设定结果映射
<resultMaps>
//将两个结果列和对象的两个属性建立映射
<resultMap id="productResult" class="Product">
<result property="Id" column="Product_Id"/>
<result property="Description" column="Product_Description"/>
</resultMap>
</resultMaps>
//设定SQL statement
<statements>
//”?”对应着参数位置,parameterMap表示参数来源,cacheModel表示缓存参数设定
<select id="GetProduct" parameterMap="productParam" cacheModel="productCache">
select * from Products where Product_Id = ?
</select>
</statements>
//设定传入参数
<parameterMaps>
//将statement中的?与对象的Id属性建立映射
<parameterMap id="productParam" class="Product">
<parameter property="Id"/>
</parameterMap>
<parameterMaps>
</sqlMap>
许多敏捷型开发人员会先写成EX3.1的形式,之后再添加诸如缓存等等的特性。当你将映射从EX3.1改变成EX3.2的形式之后,虽然变的更长更复杂,但与程序代码之间的耦合降至了最低。
A single Data Map definition file can contain as many Cache Models, Type Aliases, Result Maps, Parameter Maps, and Mapped Statements (including stored procedures), as you like. Everything is loaded into the same configuration, so you can define elements in one Data Map and then use them in another.
EX3.3 Statement element syntax(句法)
<statement id="statement.name"
[parameterMap="parameterMap.name"]
[parameterClass="alias"]
[resultMap="resultMap.name"]
[resultClass="class.name|alias"]
[listClass="class.name|alias"]
[cacheModel="cache.name"]
[extends="statement.name"]
>
select * from Products where Product_Id = [?|#propertyName#]
order by [$simpleDynamic$]
</statement>
中括号内的项是可选的,甚至有些项之间是互相排斥的。
(三)statement详细描述
1. statement类型
statement元素可以说是一种万能元素,任何sql statement都可以在此元素标记范围内定义。这很好,但更多特定的元素类型提供了更好的检错及其他的一些功能。如第一章示例中的insert元素。下表中总结了statement不同类型的元素和他们支持的属性、特殊字段。
The six statement-type elements
Statement Element |
Attributes |
Child Elements |
Methods |
<statement> |
id parameterClass resultClass listClass parameterMap resultMap cacheModel |
All dynamic elements |
Insert Update Delete All query methods |
<insert> |
id parameterClass parameterMap |
All dynamic elements <selectKey> <generate> |
Insert Update Delete |
<update> |
id parameterClass parameterMap extends |
All dynamic elements <generate> |
Insert Update Delete |
<delete> |
id parameterClass parameterMap extends |
All dynamic elements <generate> |
Insert Update Delete |
<select> |
id parameterClass resultClass listClass parameterMap resultMap cacheModel extends |
All dynamic elements <generate> |
Insert Update Delete |
<procedure> |
id parameterMap resultClass resultMap cacheModel |
All dynamic elements |
Insert Update Delete All query methods |
2.存储过程
iBATIS将存储过程视作另一种statement类型,使用方法如下所示:
EX3.4 A Data Map using a stored procedure
<!-- Microsot SQL Server -->
<procedure id="SwapEmailAddresses" parameterMap="swap-params">
ps_swap_email_address
</procedure>
...
<parameterMap id="swap-params">
<parameter property="email1" column="First_Email" />
<parameter property="email2" column="Second_Email" />
</parameterMap>
此例的功能是交换数据库的两列中存储的两个Email地址,当然同时也对参数对象进行同样操作。The parameter object is only modified if the parameter mappings mode attribute is set to InputOutput or Output. Otherwise they are left unchanged. Of course, immutable parameter objects (e.g. String) cannot be modified.//????
NOTE:对于.NET而言,parameterMap是必需的,DBType, parameter direction, size, precision, scale等属性通常由框架依据你的供应商自动去发现。(基于CommandBuilder)如果你的存储过程没有参数,则此属性为可选的。
3.SQL片段的重用
<sql id="selectItem_fragment">
FROM items
WHERE parentid = #value#
</sql>
<select id="selectItemCount" parameterClass="int" resultClass="int">
SELECT COUNT(*) AS total
<include refid="selectItem_fragment"/>
</select>
<select id="selectItems" parameterClass="int" resultClass="Item">
SELECT id, name
<include refid="selectItem_fragment"/>
</select>
<sql>标记内的是要重用的片段,<include>标记指示要重用哪个片段。在很多情况下,也可以使用statement标记中的extends属性达到同样的目标。
4.调解XML标记冲突
由于在文档中同时使用了SQL语言和XML语言,因此会出现冲突。最常见的冲突就是>和<标记的冲突。SQL语言将此用作运算符,而XML以之为标记。一个简单的解决方法是,构建CDATA元素。
<statement id=”SelectPersonsByAge” parameterClass=”int” resultClass=”Person”>
<![CDATA[
SELECT * FROM PERSON WHERE AGE>#value#
]]>
</statement>
5.自动生成键
许多数据库系统支持自动生成主键域,作为一种扩展功能。有些供应商采用预生成模式,有些采用后生成模式。不管哪一种,你都可以通过iBATIS技术中的<insert>元素中插入<selectKey>节的方式,获取一个预生成的主键。
<!—Microsoft SQL Server IDENTITY Column Example-->
<insert id=”insert Product-MS-SQL” parameterClass=”product”>
insert into PRODUCT (PRD_DESCRIPTION)
values (#description#)
<selectKey resultClass=”int” type=”post” property=”id”>
select @@IDENTITY as value
</selectKey>
</insert>
6.<generate>标记
使用<generate>标记,可以基于<parameterMap>元素中的参数,自动创建简单的SQL statement节。支持增删改查四种基本语句。
<parameterMaps>
<parameterMap id="insert-generate-params">
<parameter property="Name" column="Category_Name"/>
<parameter property="Guid" column="Category_Guid" dbType="UniqueIdentifier"/>
</parameterMap>
<parameterMap id="update-generate-params" extends="insert-generate-params">
<parameter property="Id" column="Category_Id" />
</parameterMap>
<parameterMap id="delete-generate-params">
<parameter property="Id" column="Category_Id" />
<parameter property="Name" column="Category_Name"/>
</parameterMap>
<parameterMap id="select-generate-params">
<parameter property="Id" column="Category_Id" />
<parameter property="Name" column="Category_Name"/>
<parameter property="Guid" column="Category_Guid" dbType="UniqueIdentifier"/>
</parameterMap>
</parameterMaps>
<statements>
<update id="UpdateCategoryGenerate" parameterMap="update-generate-params">
<generate table="Categories" by="Category_Id"/>
</update>
<delete id="DeleteCategoryGenerate" parameterMap="delete-generate-params">
<generate table="Categories" by="Category_Id, Category_Name"/>
</delete>
<select id="SelectByPKCategoryGenerate" resultClass="Category" parameterClass="Category"
parameterMap="select-generate-params">
<generate table="Categories" by="Category_Id"/>
</select>
<select id="SelectAllCategoryGenerate" resultClass="Category"
parameterMap="select-generate-params">
<generate table="Categories" />
</select>
<insert id="InsertCategoryGenerate" parameterMap="insert-generate-params">
<selectKey property="Id" type="post" resultClass="int">
select @@IDENTITY as value
</selectKey>
<generate table="Categories" />
</insert>
</statements>
NOTE:对应的SQL语句是在DataMapper实例化是就创建的,所以执行时不会有性能上的影响。使用<generate>标记不支持blobs等供应商的特殊类型,也不能简化复杂的SQL语句做到化繁为简,但它却做到了简者更简。
Important Ps:此标记的主要作用只是简化代码,不需要单独写一个statement代码段,但其实际作用并不如已有工具强大。因此当generate不能满足你的需求时,使用statement代码段代替它。
<generate>标记支持两个属性,table属性标识代码作用于的表名,by属性标识where子句中所需的字段名(为可选项)。
7. Statement元素的属性
3.3.4.1. id
Id属性为statement进行命名,且必须保证在整个SqlMap中都是唯一的标识。
3.3.4.2. 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
<!-- 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 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.3.4.5. 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>
Ps:名称与Person类中属性名相同,即可直接通过resultClass完成映射绑定
3.3.4.6. listClass
using System;
using System.Collections;
namespace WebShop.Domain
{
public class AccountCollection : CollectionBase
{
public AccountCollection() {}
public Account this[int index]
{
get { return (Account)List[index]; }
set { List[index] = value; }
}
public int Add(Account value)
{
return List.Add(value);
}
public void AddRange(Account[] value)
{
for (int i = 0; i < value.Length; i++)
{
Add(value[i]);
}
}
public void AddRange(AccountCollection value)
{
for (int i = 0; i < value.Count; i++)
{
Add(value[i]);
}
}
public bool Contains(Account value)
{
return List.Contains(value);
}
public void CopyTo(Account[] array, int index)
{
List.CopyTo(array, index);
}
public int IndexOf(Account value)
{
return List.IndexOf(value);
}
public void Insert(int index, Account value)
{
Account.Insert(index, value);
}
public void Remove(Account value)
{
Account.Remove(value);
}
}
}
<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>
其中,listClass属性标识结果集最终映射到AccountCollection类中,resultClass属性标识每一条记录映射到一个Account类中,然后添加至AccountCollection类。
3.3.4.7. cacheModel
<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>
Cache类型参考LRU,默认刷新周期为24小时一次,若insertProduct等三个代码段任意一个执行,则执行后即刻刷新。并设定最大缓存记录数为1000条。
3.3.4.8. extends
<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>
作用:援引先前已定义的代码段
8. Parameter Maps与Inline Parameters
EX8.1 一个外部的Parameter Map
<parameterMap id="parameterMapIdentifier"
[class="fullyQualifiedClassName, assembly|typeAlias"]
[extends="[sqlMapNamespace.]parameterMapId"]>
<parameter
property ="propertyName"
[column="columnName"]
[direction="Input|Output|InputOutput"]
[dbType="databaseType"]
[type="propertyCLRType"]
[nullValue="nullValueReplacement"]
[size="columnSize"]
[precision="columnPrecision"]
[scale="columnScale"]
[typeHandler="fullyQualifiedClassName, assembly|typeAlias"]
<parameter ... ... />
<parameter ... ... />
</parameterMap>
EX8.2 一个典型的Parameter Map
<parameterMap id="insert-product-param" class="Product">
<parameter property="description" />
<parameter property="id"/>
</parameterMap>
<statement id="insertProduct" parameterMap="insert-product-param">
insert into PRODUCT (PRD_DESCRIPTION, PRD_ID) values (?,?);
</statement>
如果需要援引另一个DataMap内的ParameterMap,只需在parameterMap属性中加上另一个DataMap的命名空间作为前缀,如Product.insert-product-param即可。
1)<parameterMap>属性
3.4.1.1. id
需要赋值为整个DataMap中的唯一标识。
3.4.1.2. class
确定此ParameterMap需要使用的类,全称或已定义的别名均可。需保证是个属性对象或是一个IDictionary实例。
3.4.1.3. extends
指定引用某个已有的parameterMap,作用类似于基类的继承。
2)<parameter>元素
3.4.2.1. property
参数对象的某个属性名称,或是一个Idictionary对象的一个条目的名称。
3.4.2.2. column
通常用于指定存储过程的某个参数名。
3.4.2.3. direction
通常用于确定存储过程中某个参数的数据流向类型,取值和对应描述如下所示:
Value |
Description |
Input |
input-only |
Output |
output-only |
InputOutput |
Bidirectional |
3.4.2.4. dbType
某些数据库中支持的类型与.NET支持的类型有出入,或有些列为可空的,则需要定义dbType属性进行更准确的指定。
3.4.2.5. type
用于指示property属性的类型(即C#中的类型),以便对Output类型的参数进行回传赋值。
3.4.2.6. nullValue
针对如int、float等值类型的变量参数,设定一个特殊值,使得系统发现此值时,自动向数据库写入NULL,而非设定的值。
3.4.2.7. size
此属性设定指定列内可输入的最大值。
3.4.2.8. precision
此属性用于设定列内可接受的值的最大位数。
3.4.2.9. scale
此属性用于设定列内可接受的精确值可精确到小数点后多少位。
3.4.2.10. typeHandler
允许使用数据库不支持的自定义类,在类中需定义数据库中数据类型和此自定义类型的转换方式。(初步理解是这样,不大确定……)
3)Inline Parameter Maps
EX3.1 一个使用inline parameters的statement
<statement id="insertProduct" parameterClass="Product">
insert into PRODUCT (PRD_ID, PRD_DESCRIPTION)
values (#id#, #description#)
</statement>
EX3.2 一个使用带有类型的inline parameters的statement
<statement id="insertProduct" parameterClass="Product">
insert into PRODUCT (PRD_ID, PRD_DESCRIPTION)
values (#id:int#, #description:VarChar#)
</statement>
EX3.3使用带有类型的inline parameters的statement,同时定义了nullValue
<statement id="insertProduct" parameterClass="Product">
insert into PRODUCT (PRD_ID, PRD_DESCRIPTION)
values (#id:int:-999999#, #description:VarChar#)
</statement>
EX3.4 使用了包含property, type, dbType,和null value的内联替换句法的statement
<update id="UpdateAccountViaInlineParameters" parameterClass="Account">
update Accounts set
Account_FirstName = #FirstName#,
Account_LastName = #LastName#,
Account_Email =#EmailAddress,type=string,dbType=Varchar,
nullValue=no_email@provided.com#
where Account_ID = #Id#
</update>
NOTE:
在使用内联参数时,若规定了nullValue 属性,必须同时规定dbType属性。同时为了使空值实现透明往返,你必须在result Map中也定义好nullValue。
在小项目中,内联参数用起来很方便,但若在一个较复杂的statement中有较多类型描述和nullValue,优质的外部parameterMap用起来更方便。
4)标准类型的参数
<statement id="getProduct" parameterClass="System.Int32">
select * from PRODUCT where PRD_ID = #value#
</statement>
5) Map或Idictionary类型的参数
<statement id="getProduct"
parameterClass="System.Collections.IDictionary">
select * from PRODUCT
where PRD_CAT_ID = #catId#
and PRD_CODE = #code#
</statement>
如果一个HashTable的实例作为参数被传入,则其中必须包含key值为catId、code的键值对,其中的value值必须与对应列的类型相适合。
9.Result Maps
<resultMap>元素的结构:
<resultMap id="resultMapIdentifier"
[class="fullyQualifiedClassName, assembly|typeAlias"]
[extends="[sqlMapNamespace.]resultMapId"]>
<constructor >
<argument property="argumentName"
column="columnName"
[columnIndex="columnIndex"]
[dbType="databaseType"]
[type="propertyCLRType"]
[resultMapping="resultMapName"]
[nullValue="nullValueReplacement"]
[select="someOtherStatementName"]
[typeHandler="fullyQualifiedClassName, assembly|typeAlias"] />
</constructor >
<result property="propertyName"
column="columnName"
[columnIndex="columnIndex"]
[dbType="databaseType"]
[type="propertyCLRType"]
[resultMapping="resultMapName"]
[nullValue="nullValueReplacement"]
[select="someOtherStatementName"]
[lazyLoad="true|false"]
[typeHandler="fullyQualifiedClassName, assembly|typeAlias"]
/>
<result ... .../>
<result ... .../>
// Inheritance support
<discriminator column="columnName"
[type|typeHandler="fullyQualifiedClassName, assembly|typeAlias"]
/>
<subMap value="discriminatorValue"
resultMapping="resultMapName"
/>
<subMap .../>
</resultMap>
NOTE:
像parameterClass一样,result Class也必须是一个.NET对象或是Idictionary实例。