ibatis

1.1总述

ibatis数据映射框架使得Java应用和.net应用和数据库连接很简单,ibatis数据映射利用存储过程或者使用XML描述的SQL和对象联系。通过对象关系映射工具的iBATIS数据映射的最大的优点就是简单。为了使用iBATIS数据映射,你需要依赖你自己的对象,XML和SQL.这里有一点你不知道的需要学习。使用iBATIS数据映射SQL和存储过程有很高的效率。

1.2内容简介

这篇教程涵盖了.NET应用iBATIS数据映射。Java应用提供了相同的功能,但是API有一些改变。因为iBATIS依赖一个XML描述来创建映射,大部分功能JAVA和.NET都应用。对于安装的介绍,看.NET开发指导部分。

一个教程也是有效的。我们建议你在读这篇指南之前回顾下你的平台的教程。

建议:如果你想得到这篇指南的最新版本,请看ibatis wili faq。FAQ实体解释了你怎样能访问我们的SVN资源仓库,然后生成数据映射最新开发版本的CHM和PDF文件。

2大图片

2.1介绍

ibatis是一个简单但是完全的框架,它使得你很简单的映射你的对象到你的SQL语句或者是存储过程。ibatis框架的目标是使用20%的代码获得数据访问的80%功能。

2.2它做了那些?

开发者在一个应用中经常创建对象之间的映射。

2.3它是怎样工作的

你的项目平台已经提供了访问数据库的库文件,然后通过SQL语句或者是存储过程。但是开发者发现几件事情一直都很难,那就是:

1、把SQL语句和项目代码分开;

2、通过输入元素到库类和提取输出;

3、把数据访问和业务逻辑分开;

4、缓存经常使用的数据直到它改变;

5、管理事务和线程。

ibatis数据映射解决了以上问题,更多的是,通过使用XML文档创建对象和SQL语句或者是存储过程之间的映射。这个对象可以是字典或是属性对象。

注意:这个对象不需要是特殊对象层次的一部分或是实现特殊接口。无论你已经使用了什么都可以很好的工作。


ibatis数据映射的工作流程

下面是工作流程的一个高层次的描述:

1、提供一个参数,可以是一个对象或者是本地类型。这个参数可以用来设置SQL语句或者是存储过程运行时的值。如果运行值不需要,这个参数可以省去。

2、通过传递参数和你XML文件中描述的SQL语句或存储语句中的名称进行映射。这一步的发生是魔幻般的。这个框架将会准备SQL语句或者存储过程,运行时通过你的参数设置,执行存储或语句,然后返回结果。

3、在更新中,返回受影响的行数。在查询中,返回一个对象或者是对象的集合。像参数,结果对象,或者是对象集合可以是普通的对象或者是本地类型。

下面是你的代码应该怎样插入一个LineItem对象到你的数据库:

C#

Mapper.Instance().Insert("InsertLineItem",lineItem);

如果你的数据是自动生成主键,可以通过以下方法返回生成的主键:

C#

int myKey=Mapper.Instance().Insert("InsertLineItem",lineItem);

这个InsertLineitem文件是:

<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>
<selectKey>从SQL Server database中返回一个自动生成的主键。
如果你需要选择多行,ibatis可以返回一个对象集合:
C#
IList productList = Mapper.Instance().QueryForList("selectProduct",categoryKey);

或者是一个,你需要的是:
C#
Product product = Mapper.Instance().QueryForObject("SelectProduct",productKey) as Product;

当然,还有更多。


2.4ibatis是你项目的最好选择吗?

ibatis是数据映射工具。他的角色是映射数据库中的列和对象的属性,如果你的应用时基于业务对象(包括映射和层次对象),那么ibatis可能是一个好的选择。当你的应用是分层的,那么ibatis是更好的选择,所以业务层和你的用户接口层分开了。

在这些条件下,另外一个好的选择将会是对象关系映射工具(OR/M工具),比如NHibernate。其他的产品在这种分类中是Apache ObjectRelationalBridge和.net。一个OR/M工具提前或在运行时生成所有的或者是大部分的SQL语句。这些产品被叫做OR/M工具,因为它们尝试映射一个对象到一个关系数据库。

ibatis不是OR/M工具。ibatis帮助你映射对象到存储过程或者SQL语句。基础数据库是不合适的。一个OR/M工具是很伟大的如果你能够映射你的对象到数据库表。但是它们不能应用于当你的对象不再被存储在数据库表中而是数据视图。如果你能够写一个语句或者是存储过程来显示你的列,忽略它们是怎样存储的,ibatis能够做到剩下的工作。

所以,你怎样决定选择OR/M还是数据映射?综上,最好的建议是通过以上两种方法实现你的项目中最典型的一部分,然后再决定。但是,大体上,OR/M是一个很好的事情,当你包含以下:

1、能够完全控制你的数据库实现;

2、不需要一个数据库管理员或者SQL专家;

3、需要在数据库外保存对象模型图;

同样的,最好的时间使用数据映射,像iBATIS,当:

1、你不需要完全通过数据库实现控制,或者想要继续访问遗留数据作为存储;

2、你需要数据库管理员或者是SQL专家;

3、数据库被用作问题域的模型,应用的主要角色是帮助客户使用数据库模型。

最后,你需要决定对于你的项目最好的优势。如果一个OR/M公斤工作的更好,那很好。如果你下一个项目有不同的需求,那么我们希望你给iBATIS另外的查看。如果iBATIS的工作适合你现在的项目,那么如果有任何问题加入iBATIS用户列表。


3使用数据映射工作

3.1介绍

如果你想要知道如何配置和运行iBATIS,看你平台上的开发者指导。但是如果你想要知道iBATIS真正是怎么工作的,那么请继续。

数据映射定义文件是有趣东西发生的地方。这里,你定义了你的应用怎样和你的数据库交流。如上所述,数据映射定义是一个XML描述文件。通过使用iBATIS提供的服务程序,XML描述被表达成客户对象。为了访问你的数据映射,你的应用调用客户对象和传递你的SQL语句需要的名字。

使用iBATIS的真实工作在应用代码中不需要太多,但是在XML需要iBATIS表达。代替了在源代码中工作,你需要XML描述代替。好处是XML更好的适用了映射你的对象属性和数据库实体。至少,那是我们利用我们自己的应用的经验。当然,你的好处是多样的。


3.2数据映射定义文件中都有什么?

如果你读了教程,你已经看了很多的简单的数据映射例子,像下面的例子:

<?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实例中读取一些属性,然后把值融入到SQL语句中。value-add是我们的SQL从我们的项目代码中分开的,我们可以传我们的LineItem实例直接到库方法:

C#
Mapper.Instance().Insert("InsertLineItem",lineItem);
不要大惊小怪。

在上面的例子中,我们使用了SQL 别名映射列到我们对象的属性,一个iBATIS内联参数增加一个运行时值。


一个快速看内联参数:

我们有一个映射语句元素像下面一样:

<statement id="InsertProduct">
  insert into Products (Product_Id, Product_Description) 
  values (#Id#, #Description#);
</statement>
上面的内联参数在这里是#Id# and #Description#。让我们也可以说我们有一个带有Id和Description属性的对象。如果我们分别设置了对象属性值为5和"dog",然后传递对象到映射语句,最终我们会形成一个运行时的查询,像下面一样:

insert into Products (Product_Id, Product_Description) values (5, ‘dog');
关于更多的内联参数,看3.4节。

如果你想要缓存查询的结果,或者我们不想使用SQL 别名或者名称参数。下面的例子展示了一个数据映射到指定的缓存,使用属性<parameterMap>和<resultMap>来保持SQL的简洁。

<?xml version="1.0" encoding="UTF-8" ?>
  <sqlMap namespace="Product" 
xmlns="http://ibatis.apache.org/mapping" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >

  <alias>
    <typeAlias alias="Product" type="Example.Domain.Product, Example.Domain" />
  </alias>

  <cacheModels>
    <cacheModel id="productCache" implementation="LRU">
      <flushInterval hours="24"/>
      <property name="CacheSize" value="1000" />
    </cacheModel>
  </cacheModels>

   <resultMaps>
    <resultMap id="productResult" class="Product">
      <result property="Id" column="Product_Id"/>
      <result property="Description" column="Product_Description"/>
    </resultMap>
   </resultMaps>

  <statements>
    <select id="GetProduct" parameterMap="productParam" cacheModel="productCache">
      select * from Products where Product_Id = ?
    </select>
  </statements>

  <parameterMaps>
    <parameterMap id="productParam" class="Product">
      <parameter property="Id"/>
    </parameterMap>
  <parameterMaps>

</sqlMap>
上面的例子中<parameterMap>映射了SQL中的“?”到 product的Id属性。<resultMap>映射了列到我们的对象属性。<cacheModel>保持了最后查询的1000条记录结果到我们常用内存中24小时。

上面的例子比第一个例子要长复杂,但是考虑下你从返回中得到的,看起来像是一个很公平的交易。

很多灵活的开发者像第一个例子一样开始,然后再添加更多的特征像缓存。如果你把数据映射从第一个例子改成第二个例子,你不得不改变应用中的代码。你可以开始简单的,当你真正需要时增加复杂的。

一个单独的数据映射定义文件可以包含很多, Cache Models, Type Aliases, Result Maps, Parameter Maps, 和Mapped Statements,只要你想。每一件事加载相同的配置文件,所以你可以在一个数据映射中定义元素,然后在另一个中使用。使用描述和组织S语句,映射属性到你的应用中通过找到一些逻辑方法。


3.3映射语句

映射语句可以包含任何SQL语句,你可以使用Parameter Maps和Result Maps映射你的输入和输出。(一个存储过程是一个特殊形式的语句。看3.3.1节和3.3.2节)

如果你的案例是简单的,映射语句可以直接引用参数和结果类。映射语句支持缓存通过引用一个缓存模型元素。下面的例子展示了一个语句元素的语法:

<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>

在上面的例子中,中括号[]中的部分是可选的,有些选项是互相排斥的。像下面例3.4子一样简单也是正确的。

<statement id="InsertTestProduct" >
  insert into Products (Product_Id, Product_Description) values (1, "Shih Tzu")
</statement>
3.4明显是不可能的,除非你是在测试。但是它确实显示了你可以使用ibatis执行任意的SQL语句。还有,你将会使用对象映射特征使用 ParameterMaps(3.4节)和Result Maps(3.5节)
因为那是很奇妙的发生了。

3.3.1语句类型

<statement>元素是一个一般的“catch all”元素,你可以用于任何类型的SQL语句。一般的它使一个很好的方法来使用明确语句类型的元素。明确的元素提供了较好的错误检查
甚至更多的功能。比如,插入语句可以返回数据库自动生成的主键。表3.1总结了语句类型元素和他们支持的属性和特征。

表3.1
Statement ElementAttributesChild ElementsMethods
<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>
All query methods
<procedure>
id 
parameterMap 
resultClass 
resultMap
cacheModel
All dynamic elements 
Insert 
Update 
Delete 
All query methods
通过语句类型元素使用多样的属性在3.3.4节介绍。
3.3.2存储过程
ibatis数据映射对待存储过程为另一个语句类型。例子3.5展示了一个简单的数据映射包括了存储过程。
例子3.5
<!-- 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>

<!-- Oracle with MS OracleClient provider -->
<procedure id="InsertCategory" parameterMap="insert-params">
 prc_InsertCategory
</procedure>
... 
<parameterMap id="insert-params">
 <parameter property="Name"       column="p_Category_Name"/>
 <parameter property="GuidString" column="p_Category_Guid" dbType="VarChar"/>
 <parameter property="Id"         column="p_Category_Id"   dbType="Int32"   type="Int"/>
</parameterMap>

<!-- Oracle with ODP.NET 10g provider -->
<statement id="InsertAccount" parameterMap="insert-params">
 prc_InsertAccount
</statement>
... 
<parameterMap id="insert-params">
 <parameter property="Id"           dbType="Int32"/>
 <parameter property="FirstName"    dbType="VarChar2" size="32"/>
 <parameter property="LastName"     dbType="VarChar2" size="32"/>
 <parameter property="EmailAddress" dbType="VarChar2" size="128"/>
</parameterMap>
例子3.5后面的思想是调用存储过程SwapEmailAddress将会交换两个email地址在数据库表中的两列,在参数对象中。如果参数映射模型属性设置了InputOutput 或者 Output参数对象才可以修改。
然而,他们没有被修改。当然,多参数对象是不能被修改的。

3.3.3SQL
如果你不适用存储过程,语句类型元素最重要的一部分是SQL。你可以使用任何SQL语句只要是你的数据库系统是有效的。因为ibatis通过标准的库文件传递SQL,所以你可以使用任何
语句,只要你能够使用的。你可以使用任何你的数据库系统支持的功能,甚至发送多个语句,只要你的驱动类或者提供者支持他们就可以。
如果标准的,静态的SQL不够,ibatis可以帮助你创建一个静态的SQL语句。看3.9节关于更多的动态SQL.

3.3.3.1重用SQL语句

当写SQLMaps时,你经常会重复写很多的SQL语句,比如一个from语句或者限制语句。ibatis提供了一个简单的但是强大的标签可以重用他们。为了简单,让我们假设我们想要获得一些记录还有我们想要计数。正常的是,你将会像下面例子3.6一样:

例子3.6

<select id="SelectItemCount" resultClass="int">
SELECT COUNT(*) AS total
FROM items
WHERE parentid = 6
</select>

<select id="SelectItems" resultClass="Item">
SELECT id, name
FROM items
WHERE parentid = 6
</select>
为了消除重复,我们可以使用<sql>和<include>标签。<sql>标签包括了重用的部分,<include>标签包括了在语句中声明的部分。例如,下面的例子3.7:

<sql id="selectItem_fragment">
FROM items
WHERE parentid = 6
</sql>

<select id="selectItemCount" resultClass="int">
SELECT COUNT(*) AS total
<include refid="selectItem_fragment"/>
</select>

<select id="selectItems" resultClass="Item">
SELECT id, name
<include refid="selectItem_fragment"/>
</select>
<include>标签是命名空间,所以你可以引用重用部分,当他们在另一个映射语句中,然而,由于ibatis加载SqlMaps的方式,被包括的语句应该在包括的语句之前。重复语句被包括和处理在查询语句中,所以参数可以这么使用:

<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>
注意:在很多情况下,你也可以使用extends属性在语句标签中来完成相同的目标。

3.3.3.2消除XML符号

因为你在一个文档中结合了SQL和XML,有时可以引起冲突。最常见的冲突是大于号(>)和小于号(<)。SQL语句使用这些符号作为操作符,但是在XML中被看做预留符号。一个简单的解决方案是消除SQL语句使用XML中预留的符号利用一个CDATA元素。例子3.8展示了这个问题:

例子3.8

<statement id="SelectPersonsByAge" parameterClass="int" resultClass="Person">
  <![CDATA[ 
     SELECT * FROM PERSON WHERE AGE > #value# 
  ]]>
</statement>
3.3.3.3自动生成主键

很多数据库系统支持自动生成主键,作为一个供应商的扩展。一些供应商会先自动生成主键,比如(Oracle)还有一些供应商是提交时生成主键,比如(SQL Server和Mysql)。在其他的情况下,你可以使用<selectKey>在<insert>语句中提前生成主键,下面的例子3.9就展示了:

例子3.9

<!—Oracle SEQUENCE Example using .NET 1.1 System.Data.OracleClient --> 
<insert id="insertProduct-ORACLE" parameterClass="product"> 
  <selectKey resultClass="int" type="pre" property="Id" > 
     SELECT STOCKIDSEQUENCE.NEXTVAL AS VALUE FROM DUAL
  </selectKey> 
  insert into PRODUCT (PRD_ID,PRD_DESCRIPTION) values (#id#,#description#) 
</insert>

<!— Microsoft SQL Server IDENTITY Column Example --> 
<insert id="insertProduct-MS-SQL" parameterClass="product"> 
  insert into PRODUCT (PRD_DESCRIPTION)
  values (#description#) 
 <selectKey resultClass="int" type="post" property="id" > 
   select @@IDENTITY as value
 </selectKey>
</insert>

<!-- MySQL Example -->
<insert id="insertProduct-MYSQL" parameterClass="product"> 
  insert into PRODUCT (PRD_DESCRIPTION)
  values (#description#) 
 <selectKey resultClass="int" type="post" property="id" > 
   select LAST_INSERT_ID() as value
 </selectKey>
</insert>

3.3.3.4<generate>标签

你可以使用ibatis执行任何SQL语句。当对于一个语句的请求又简单又明显时,你可能甚至不需要写一个SQL语句。<generate>标签能够用于自动创建简单的SQL,基于<parameterMap>元素。基本的增删改查是被支持的。对于一个查询,你可以查询所有的或者是按照主键查询。下面的例子3.10展示了增删改查的语句。

例子3.10

<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>
注意,上面的SQL是自动生成的,所以在执行时没有任何的性能影响。

标签生成了ANSI SQL,可以在任何一致的数据库中可以工作。特殊类型,比如blobs是不被支持的。

3.3.3.4.1<generate>标签属性

这个标签支持两个属性:

AttributeDescriptionRequired
tablespecifies the table name to use in the SQL statement.yes
byspecifies the columns to use in a WHERE clauseno
3.3.4语句类型元素属性

六个语句类型元素有多种属性。独特的属性在下面被描述:

3.3.4.2parameterMap

一个 Parameter Map定义了一个排好序列的值映射标准占位符"?",参数化得查询语句。例子3.9展示了<parameterMap>和一个返回<statement>

例子3.11

<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>
上面的例子中,Parameter Map描述了两个有序的参数匹配SQL语句中的两个占位符。第一个"?"被id属性的值替代。第二个被属性description属性值替代。

ibatis也支持名称,内联参数。然而,Parameter Maps当SQL必须是标准形式或者到额外信息需要提供时是很有用的。关于更多的Parameter Maps看3.4节。

3.3.4.3parameterClass

如果一个parameterMap属性没有被明确,你应该明确parameterClass,使用内联参数。parameterClass属性的值可以是一个Alias类型或者是一个类的全称。例子3.10展示了一个语句使用一个全称和别名的。

例子3.12

<!-- 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.4resultMap

一个Result Map让你可以从一个查询的结果控制要返回的数据,还有控制列是怎样映射到对象属性的。例子3.13展示了<resultMap>元素和一个返回语句元素。

例子3.13

<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>
上面的例子,SQL查询结果将会映射一个Product类的实例通过使用"select-product-result"的<resultMap>.<resultMap>说明了id属性来自于PRD_ID列,description属性来自于PRD_DESCRIPTION列。

在上面的例子中,注意使用“select *”也是被支持的。如果你想要所有的列,你不需要分别的映射他们。(尽管很多开发者认为明确特定的列总是一个很好的行为)。

3.3.4.5resultClass

如果resultMap没有明确,你应该使用resultClass明确。resultClass属性的值可以是Type别名或者是类名的全称。指定的类将会自动的映射列到结果中,基于结果元数据。下面的例子展示了一个使用resultClass属性的语句元素。

例子3.14

<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>
在上面的例子中,类Person的属性包括Id, FirstName,LastName,BirthDate,WeightInKilograms, 和HeightInMeters.每一个都是通过SQL查询语句使用“as”关键字响应到列的别名。当被执行时,一个Person对象被实例化通过映射对象属性名称到列名通过查询。

使用SQL别名来映射列和属性保存定义的<resultMap>元素,但是应该有限制。这里没有方法来明确输出列的类型,这里也没有方法来自动加载相关的数据,比如复杂的属性,这里有一个微小的性能影响,通过访问结果元数据。使用别名这种方式有可能混淆了数据库逻辑,使查询更难读取和维护。你可以克服这些限制利用一个明确的Result Map。

3.3.4.6listClass

另外为了提高返回一个对象列表的能力,数据映射支持使用一个强类型的自定义集合:一个类实现了System.Collections.CollectionBase 抽象类。下面的CollectionBase类使用了数据映射。

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);
  }
 }
}
一个CollectionBase类能够被明确利用一个查询语句通过listClass属性。listClass属性值可以是一个Type别名或者是类名的全称。语句应该表明resultClass因此数据映射知道怎样处理集合中的对象类型。resultClass被明确将会自动的映射到结果中的列,基于结果元数据。下面的例子展示了使用listClass属性的语句元素。

例子3.16

<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.7cacheModel

如果你需要缓存查询结果,你可以明确一个缓存模型作为<statement>元素的一部分。例子3.15展示了<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>
上面例子中的缓存使用了LRU引用类型,每隔24小时刷新或者当更新语句被执行时属性。

3.3.4.8extends

当写SQL时,你经常会有重复的SQL。ibatis提供了一个简单然而强大的属性来重用。

<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.4参数映射和内联参数

大多数SQL语句是很有效的,因为我们可以在运行时给SQL传递值。有些人想要一个ID是42的数据记录,我们就需要把ID融入到一个查询语句中。一个列表或者多个参数在运行时都可以传递。每一个占位符都可以被替换。这是简单,但是费时,因为一些开发者花费很多的时间来保证任何事情都是同步的。

注意:假设在内联参数中部分被简单的涉及到,那么这些可以自动映射属性到被命名的参数。很多ibatis开发者更喜欢这种方式。但是其他人更喜欢标准的,通过使用参数映射的匿名方法的SQL。有时人们需要保留单纯的SQL语句;有时他们需要描述明确通过参数映射,因为数据库或者提供需要使用的明确信息。一个参数映射定义了一个已排好序列的值匹配查询语句中的占位符。然而通过映射的属性明确仍然需要正确的顺序,每一个参数被命名。你可以以任何顺序计算潜在的类,Parameter Map保证了每一个值都是正确的顺序。


Parameter Map可以被当做一个外部元素和内联参数被提供。例子3.16展示了一个外部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>
在上面的例子中,中括号[]部分是可选的。parameterMap元素唯一被要求的属性是id。class属性是可选但是推荐应该选上。class属性可以帮助验证输入参数和优化性能。例子3.7展示了一个典型的<parameterMap>

<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>
注意:Parameter Map名称总是在数据映射定义文件中。你可以引用另一个数据映射定义文件中的,通过使用数据映射的命名空间和parameger Map的id。

3.4.1<parameterMap>属性

<parameterMap>元素接受3个属性:id(必须的),class(可选的),extends(可选的)。

3.4.1.1id

必须的id属性提供了Data Map中的<parameterMap>的一个唯一的标识。

3.4.1.2class

可选的class属性明确了一个对象类型通过使用<parameterMap>。类名的全称或者一个别名必须被指定。任何类都可以被使用。

注意:参数类必须是对象的一个属性或者层次实例。

3.4.1.3extends

可选项extends属性可以被设置到另一个parameterMap名称。所有的父parameterMap的属性将会被包含在这个parameterMap的一部分。

3.4.2<parameter>元素

<parameter>元素有一个或多个参数子元素来映射对象属性到SQL语句中的占位符。接下来就描述每一个属性。

3.4.2.1property

<parameter>属性property是属性的名称或者是对象参数的属性。它也可能是一个实体的名称在一个层次对象中。名称可以被用于多于一次依赖它需要的次数。

3.4.2.2column

column属性用于定义参数的名称通过一个存储过程。

3.4.2.3direction

direction属性可以被用来表明存储过程的参数命令。

ValueDescription
Inputinput-only
Outputoutput-only
InputOutputbidirectional
3.4.2.4dbType

dbType属性被用来明确数据库列的参数类型通过属性设置。对于每一个明确的选项,一些ADO.NET提供者不能决定一个列的类型,它的类型必须被指定。

这个属性平常事不被要求的如果列是空值。尽管,另一个原因使用dbType属性明确了日期类型,然而,.net仅仅有一个日期值类型(System.DateTime),大多数的SQL数据库多于一个。通常,一个数据库至少有三个不同的类型(DATE,DATETIME,TIMESTAMP).为了能够正确的映射值,你应该需要指定列的dbType。

注意:大多数提供者仅仅需要dbType明确允许为空的列。

dbType属性可以设置任何String值在确定的数据类型枚举中匹配一个常量。

3.4.2.5type

type属性用于明确property参数的CLR类型。这个属性很有用,当你传递InputOutput和Output参数到存储过程中。这个框架使用了确定的类型到合适的处理中,设置参数对象属性到存储输出。

正常的,type能够得到从一个属性通过反射,但是确定的映射使用对象,比如一个Map不能提供property 类型到框架中。如果属性类型没有设置,框架不能决定类型,可能被假设为一个Object。第6节描述了CLR类型和一个有效的别名,在框架中是支持的。


3.4.2.6nullValue

nullValue属性能够被设置任何有效的值。nullValue属性被用于明确替代一个空值。意思就是当在检查一个对象的属性时,一个NULL将被写到数据库中。这允许你可以使用一个不可思议的null在你的应用中,当一些类型不支持null值时,比如(Int,double,float)。当这些属性包括一个匹配的null值时,一个NULL将会被写到数据库中。


3.4.2.7size

size属性设置了在列中的数据的最大长度。

3.4.2.8precision

precision属性设置数字的最大值用现有的属性值。

3.4.2.9scale

scale属性设置十进制数用于解决属性值。

3.4.2.10typeHandler

typeHandler属性允许使用一个自定义类型的处理器。这允许你扩展数据映射的能力在处理类型明确到你的数据库供应商,而不是通过你的数据库供应商处理。或者仅仅发生在你的应用设计的一部分。你可以创建自定义类型处理器来存储和取回Boolean值和从你数据库中指导。

3.4.3内联参数映射

如果你更喜欢使用内联参数代替参数映射,你可以添加额外的类型信息。内联参数映射语法让你嵌入属性名称,属性类型,列类型,null替换。下面的四个例子展示了内联参数的使用。

例子3.20一个<statement>使用内联参数

<statement id="insertProduct" parameterClass="Product">
  insert into PRODUCT (PRD_ID, PRD_DESCRIPTION)
  values (#id#, #description#)
</statement>
下面的例子描述了怎样声明内联参数的dbTypes。

例子3.21一个<statement>使用一个内联参数映射利用类型。

<statement id="insertProduct" parameterClass="Product">
  insert into PRODUCT (PRD_ID, PRD_DESCRIPTION)
  values (#id:int#, #description:VarChar#)
</statement>
下面的例子描述了在声明的内联参数中怎么声明类型和null替换

例子3.22 一个<statement>使用一个内联参数映射利用一个null替换。

<statement id="insertProduct" parameterClass="Product">
  insert into PRODUCT (PRD_ID, PRD_DESCRIPTION)
  values (#id:int:-999999#, #description:VarChar#)
</statement>
像Java的数据映射,这里有另外一种选择内联语法,允许声明属性、类型、null替换。

例子3.23一个<statement>使用带有property、type、null替换的内联语法。

<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>
注意:当使用内联参数时,你不能够明确null替换在没有明确dbType。你一定要声明所有的解析顺序。

3.4.4标准类型参数



在使用中,你将会发现很多语句有一个单独的参数,经常是一个Integer或者是一个String。而不是在另一个对象中使用一个单独的值,你可以使用标准库对象(String,Integer等)直接作为参数。例子3.24描述了使用标准类型参数

例子3.24<statement>使用标准类型参数

<statement id="getProduct" parameterClass="System.Int32">
  select * from PRODUCT where PRD_ID = #value#
</statement>
假设 PRD_ID是一个数字类型,当调用这个映射语句时,一个标准的Integer对象可以传递进来。#value#参数将会被Integer实例值代替。value名称是一个简单的占位符,你可以使用另一个你选择的标示符。Result Maps也支持原始类型作为结果。

为了你方便,原始类型可以通过框架别名。例如:int可以用于Integer。对于一个完全的list,看3.6节,“Parameter Maps和Result Maps支持的类型”。

3.4.5Map或者层次类型参数

你也可以传递一个层次实例作为一个参数对象。这通常是一个HashTable。例子3.25描述了一个使用层次实例的参数类。

例子3.25

<statement id="getProduct" parameterClass="System.Collections.IDictionary">
  select * from PRODUCT
  where PRD_CAT_ID = #catId#
  and PRD_CODE = #code#
</statement>
在上面的例子中,注意SQL在这种映射语句中看起来像其他的。这和使用内联参数没有区别。如果一个HashTable实例被传递进来,它一定包含了catId和code的键。通过这些键引用值一定是列的适当类型,仅仅作为他们传递的一个属性对象。

为了你的方便,IDictionary类型可以使用框架别名。于是map或者HashTable可以使用System.Collections.HashTable。对于一个完全的别名集合,看3.6节。


3.5Result Maps

通过3.4节描述的Parameter Maps和内联参数,在一个数据库查询中映射对象属性到参数。Result Maps结束了通过映射数据查询到对象属性的结果工作。仅次于Mapped Statements,Result Map可能是一个大多数通常被使用的和大多数最重要的。

一个Result Map让你可以控制从一个查询结果中提取怎样的数据,列怎样映射到对象属性。一个Result Map可以描述列类型,一个null值替换,和复杂的属性映射包括集合,例子3.26描述了<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>
在上面的例子中,中括号中[]是可选属性。id属性是必须,它提供一个用于引用语句的名称。class属性也是必须的,它可以是一个明确的类型别名或者是一个全称的类名。这个类将会被实例化和赋值基于它包含的结果映射。

resultMap可以包含任何数量的属性映射来映射对象属性和结果元素中的列。属性映射被应用,列被读取,以他们定义的顺序。维护元素的顺序保证不同的驱动类和供应商之间的一致结果。

3.5.1扩展resultMaps

可选项扩展属性可以设置另一个resultMap名称基于基本的resultMap。所有的“super”resultMap的属性被包含为这个resultMap的一部分,从“super” resultMap的值在通过这个resultMap明确的任何值之前设置。这种功能相似于继承一个类。

3.5.2<resultMap>的属性

<resultMap>元素接受三种属性:id(必须的),class(可选的),extends(可选的)。

3.5.2.1id

必须的id属性提供了<resultMap>一个单独的标示符。

3.5.3class

可选的class属性明确了在这个<resultMap>中的对象类。类全称或者是一个明确了的别名。任何类都可以被使用。

3.5.2.3extends

可选的extends属性允许result map继承父 result map所有的属性。

3.5.2.4groupBy

可选的groupBy属性明确了一个.NET属性集合的通过resultMap创建的结果对象名称。他们被用于标示唯一的列在返回的结果集中。列和相等的值用于明确属性将会只生成一个结果对象。使用groupBy在结合嵌套的resultMaps来结果N+1查询问题。例子:“Id”或者“Desciption,Date”.

3.5.3<constructor>元素

<constructor>元素一定匹配一个结果类构造方法的标识。如果明确了,这个元素通过iBATIS被使用来映射实例和结果对象。<constructor>元素包含一个或多个<argument>子元素,子元素是map SQL结果集映射对象构造方法的参数。


<resultMap id="account-result-constructor" class="Account" >
	 <constructor>
		<argument argumentName="id" column="Account_ID"/>
		<argument argumentName="firstName" column="Account_FirstName"/>
		<argument argumentName="lastName" column="Account_LastName"/>
	</constructor>
	<result property="EmailAddress" column="Account_Email" nullValue="no_email@provided.com"/>
	<result property="BannerOption" column="Account_Banner_Option" dbType="Varchar" type="bool"/>
	<result property="CartOption"	column="Account_Cart_Option" typeHandler="HundredsBool"/>
</resultMap>
3.5.3.1argumentName

argumentName属性是构造方法中的一个参数的名称,这个构造方法将会通过Mapped Statement返回结果对象。

3.5.3.2column

column属性值是结果集中的列名称,这些值将会用于参数。

3.5.3.3columnIndex

作为一个可选项是用来增强性能的,columnIndex属性值是结果集中列的索引,结果集值将会被用于对象属性值。

3.5.3.4dbType

dbType属性用于明确结果集列的数据库列类型。尽管Result Maps没有null值的难题,但是明确了类型可以有效的用于明确映射类型比如Date属性。因为一个应用语言有一个Date值类型和SQL数据库可能会有很多。明确了date在很多情况下是很有必要的,保证了dates能正确设置。相似的,String类型可能被赋值为一个VarChar、Char或者CLOB,所以明确了类型在很多情况下都是需要的。

3.5.3.5type

type属性被用来明确CLR参数类型。通常的这可以从参数反射得出,但是明确使用对象的映射,比如一个Map不能提供框架的类型。如果属性类型没有设置,框架不能决定其类型,然后类型就假设为Object。

3.5.3.6resultMapping

resultMapping属性能够设置另一个ResultMapde 的名称用于填充参数。如果ResultMap在另一个映射文件中,你必须明确了全称。

3.5.3.7nullValue

nullMapping属性能够设置任何有效的值(基于参数类型)。结果元素的nullValue属性被用于明确一个代替空值的值。意思就是说当在一个查询结果列中值被检查,那么对象参数将会被设置为nullValue属性的值。这允许你使用任何值。

3.5.3.8select

select属性被用于描述对象和自动加载复杂属性类型直接的关系。statement属性的值一定是另一个映射语句的名称。数据库列的值,被定义为同一属性元素作为这个语句的属性将会被传递相关的映射语句作为参数。更多的关于支持原始类型和复杂属性映射/关系的讨论看后续文档。在查询语句中lazyLoad属性可以被确定。

3.5.3.9typeHandler

typeHandler属性允许使用用户自定义的类型处理器。这允许你扩展数据映射的能力在处理器类型方面,这是你的数据库供应商明确的,不是你的数据库供应商处理的,或者仅仅作为你应用设计的一部分。你可创建自定义类型处理器处理存储和恢复Booleans。

3.5.4<result>元素

<resultMap>元素包含一个或多个<result>子元素,这些子元素映射SQL结果集到对象属性。

3.5.4.1property

property属性是对象域的名称或者是结果对象的属性,将会通过Mapped Statement返回。名称可以被用于多次依赖它需要的结果次数。

3.5.4.2column

column属性值是结果集列的名称用于返回属性。

3.5.4.3columnIndex

作为一个可选项提高性能的。columnIndex属性值是结果集中的列的索引,值用于返回对象属性。这在99%的应用中是不需要的。这会牺牲了可维护性和读取的速度。一些提供着可能没有意识到任何性能的利益,然后有些人会关注速度。

3.5.4.4dbType

dbType属性用于明确结果集中列的数据库列类型,将会用于对象属性。尽管Result Maps没有null值的难题,但是确定了类型对于确定的映射类型还是很有用的,比如Date属性。因为一个应用语言有一个Date值类型,但是SQL数据库可能有多种(通常至少3个),确定了Date在一些情况下保证dates能够正确的设置还是很有必要的。相似的,String类型可能被赋值为Varchar、Char或者CLOB。所以明确了类型在很多情况下都是需要的。

3.5.4.5type

type属性用于明确被设置的参数的CLR属性类型。通常这可以通过属性的反射得出,但是确定的映射使用比如Map不能通过框架提供类型。如果属性类型没有设置,框架不能决定其类型,这个类型被推断为Object。

3.5.3.6resultMapping

resultMapping属性能够设置另一个resultMap的名称来填充属性。如果resultMap在另一个映射文件中,你必须明确了全称:

resultMapping="[namespace.sqlMap.]resultMappingId"

resultMapping="Newspaper"
<!--resultMapping with a fully qualified name.-->
resultMapping="LineItem.LineItem"
3.5.4.7nullValue

nullValue能够被设置为任何有效的值(基于属性类型)。结果元素nullValue属性用于确定一个null值替换。意思就是在一个查询结果列中值被检查时,响应的对象属性将会设置nullValue属性的值。这允许你使用任何数值。

如果你的数据库有一个NULLABLE列,但是你想要你的应用使用一个常量值代表null,你可以在Result Map中明确。

例子3.28

<resultMap id="get-product-result" class="product"> 
  <result property="id" column="PRD_ID"/>
  <result property="description" column="PRD_DESCRIPTION"/>
  <result property="subCode" column="PRD_SUB_CODE" nullValue="-9999"/>
</resultMap>
在上面的例子中如果PRD_SUB_CODE是null,那么subCode属性将会被设值为-9999.这允许你使用原始类型在你的.NET类中响应数据库中的一个空值。记住,如果你想要这个生效,那么查询和update/inserts,你必须也明确在Parameter Map中的空值。

3.5.4.8select

select属性用于描述对象和自动加载复杂属性类型直接的关系。语句属性的值必须是另一个映射语句中的名称。数据库列的值被定义为同一个属性元素作为这个语句属性将会被传递到相关的映射语句作为参数。

3.5.4.9lazyLoad

使用lazyLoad属性和select属性表明是否select语句结果应该延迟加载。这可以提供一个性能的提供通过延迟加载查询结果直到需要时才加载。延迟加载支持IList和IList<T>的实现。

延迟加载支持强类型集合通过动态代理组合。在这种情况,你必须设置listClass属性,声明所有的方法和属性,你想要代理作为虚拟化的。

例子3.29使用代理的强类型集合

[C#]

[Serializable]
public class LineItemCollection : CollectionBase 
{
	public LineItemCollection() {}

	public virtual LineItem this[int index] 
	{
		get	{ return (LineItem)List[index]; }
		set { List[index] = value; }
	}

	public virtual int Add(LineItem value) 
	{
		return List.Add(value);
	}

	public virtual void AddRange(LineItem[] value) 
	{
		for (int i = 0;	i < value.Length; i++) 
		{
			Add(value[i]);
		}
	}

	public virtual void AddRange(LineItemCollection value) 
	{
		for (int i = 0;	i < value.Count; i++) 
		{
			Add(value[i]);
		}
	}

	public virtual bool Contains(LineItem value) 
	{
		return List.Contains(value);
	}

	public virtual void CopyTo(LineItem[] array, int index) 
	{
		List.CopyTo(array, index);
	}

	public virtual int IndexOf(LineItem value) 
	{
		return List.IndexOf(value);
	}
	
	public virtual void Insert(int index, LineItem value) 
	{
		List.Insert(index, value);
	}
	
	public virtual void Remove(LineItem value) 
	{
		List.Remove(value);
	}

	public new virtual int Count
	{
		get {return this.List.Count;}
	}
}

Example�3.30.�Concrete class

[C#]

[Serializable]
public class Person
{
...
	public virtual string Name
	{
		get {return _name;}
	}
...
}
3.5.4.10typeHandler

typeHandler属性允许使用用户自定义类型处理器。这允许你扩展数据映射的能力在处理类型,处理类型是你的数据库供应商明确的,而不是通过你的数据库供应商处理的,或者它只是你应用设计的一部分。你可以创建自定义的类型处理器处理存储和恢复booleans。

3.5.5自定义类型操作

一个自定义类型操作允许你扩展数据映射的能力在处理类型中,处理类型是你的数据库供应商明确的,而不是通过你的数据库供应商处理的。或者仅仅是你应用设计的一部分。.NET框架提供了一个接口,IBatisNet.DataMappers.TypeHandlers.ITypeHandlerCallback,你可以在实现你的自定义操作中使用。

using System.Data;
using IBatisNet.DataMapper.Configuration.ParameterMapping;


namespace IBatisNet.DataMapper.TypeHandlers
{
 public interface ITypeHandlerCallback
 {
  void SetParameter(IParameterSetter setter, object parameter);

  object GetResult(IResultGetter getter);

  object ValueOf(string s);
 }
}
SetParameter方法允许你处理一个<statement>参数值在它被添加到一个IDbCommand参数中。这允许你做任何必要的类型转换和清除工作,在DataMapper开始工作。如果需要,你也可以访问底层的IDataParameter通过setter.DataParameter属性。

GetResult方法允许你比较一个String值和你期望的值,可以适当的操作。典型的,这是很有用的当转换一个null值,但是如果你的应用或者数据库不支持一个null值,你可以从根本上返回一个给定的String。当展示一个未预期的值时,你可以抛出一个适当的错误。

public class BudgetObjectCode
{
 private string _code;
 private string _description;
 private Guid _guidProperty;

...

 public Guid GuidProperty {
  get { return _guidProperty; }
  set { _guidProperty = value; }
 }

 public string GuidPropertyString {
  get { return _guidProperty.ToString(); }
  set { 
  if (value == null) {
    _guidProperty = Guid.Empty;
   }
   else {
    _guidProperty = new Guid(value.ToString());
   }
  }
 }
...
}
我们可以使用一个自定义的类型操作来清除这个类。首先,我们定义一个String代表一个null Guid值。我们可以使用一个常量在我们的null值比较用于DataMapper最终用于设置我们的域类。实现了GetResult和SetParameter方法是直截了当的,因为我们已经基本上做了同一个转化在我们的域类中。

using System;
using IBatisNet.DataMapper.TypeHandlers;

namespace BigApp.Common.TypeHandlers
{
 /// <summary>
 /// GuidVarcharTypeHandlerCallback.
 /// </summary>
 public class GuidVarcharTypeHandlerCallback : ITypeHandlerCallback
 {
  private const string GUIDNULL = "00000000-0000-0000-0000-000000000000";

  public object ValueOf(string nullValue)
  {
   if (GUIDNULL.Equals(nullValue)) 
   {
    return Guid.Empty;
   } 
   else 
   {
    throw new Exception(
     "Unexpected value " + nullValue + 
     " found where "+GUIDNULL+" was expected to represent a null value.");
   }  
  }

  public object GetResult(IResultGetter getter)
  {
   try {
    Guid result = new Guid(getter.Value.ToString());
    return result;
   } 
   catch
   {
     throw new Exception(
     "Unexpected value " + getter.Value.ToString() + 
     " found where a valid GUID string value was expected.");
   }
  }

  public void SetParameter(IParameterSetter setter, object parameter)
  {
   setter.Value = parameter.ToString();
  }

 }
}
利用我们自定义的操作,我们可以清除我们的域类,在我们的SqlMaps中使用操作。为了这样,我们在配置我们自定义的类型操作中我们有两种选择可以使用通过DataMapper。我们可以简单的添加它作为一个<typeAlias>和当我们在一个ParameterMap或者ResultMap中使用它时。

<alias>
 <typeAlias alias="GuidVarchar" 
         type="BigApp.Common.TypeHandlers.GuidVarcharTypeHandlerCallback,
               BigApp.Common"/>
</alias>
 
<resultMaps>                                    
 <resultMap id="boc-result"  class="BudgetObjectCode">
  <result property="Code" column="BOC_CODE" dbType="Varchar2"/>
  <result property="Description" column="BOC_DESC" dbType="Varchar2"/>
  <result property="GuidProperty" column="BOC_GUID" typeHandler="GuidVarchar"/>
 </resultMap>
</resultMaps>
或者我们可以明确它作为一个基本的<typeHandler>用于所有的Guid类型映射在我们的SqlMap文件。

[Our SqlMap.config]
<alias>
 <typeAlias alias="GuidVarchar" 
         type="BigApp.Common.TypeHandlers.GuidVarcharTypeHandlerCallback,
               BigApp.Common"/>
</alias>

<typeHandlers>
 <typeHandler type="guid" dbType="Varchar2" callback="GuidVarchar"/>
</typeHandlers> 


[One of our SqlMap.xml files]
<parameterMaps>
 <parameterMap id="boc-params">
  <parameter property="Code" dbType="Varchar2" size="10"/>
  <parameter property="Description" dbType="Varchar2" size="100"/>
  <parameter property="GuidProperty" dbType="Varchar2" type="guid"/>
 </parameterMap>
</parameterMaps>

<resultMaps>                                    
 <resultMap id="boc-result"  class="BudgetObjectCode">
  <result property="Code" column="BOC_CODE" dbType="Varchar2"/>
  <result property="Description" column="BOC_DESC" dbType="Varchar2"/>
  <result property="GuidProperty" column="BOC_GUID" dbType="Varchar2" type="guid"/>
 </resultMap>
</resultMaps>
3.5.6层次映射

iBATIS 数据映射支持面向对象的继承实现在你的对象模型中。这里有几类开发者选项用于映射实体类和子类到数据库结果;

1、ResultMap用于每一个类;

2、ResultMap用于一个类包括子属性;

3、ResultMap用于每一个子类扩展ResultMaps。

你可以使用最有效的映射策略通过一个SQL和查询性能当使用DataMapper的继承映射时。为了实现一个继承映射,ResultMap必须定义一个或多个列在你的查询结果集中,服务于标志ResultMap应该映射每一个结果记录到一个明确的子类。在很多情况中,你将会使用一列值在标志适当的ResultMap和子类时。这个类被作为一个鉴别器。

例如,我们有一个表定义在数据库中,这个表包括文档记录。这里有五张表列用于存储文档IDs,Titles,Types,PageNumbers和Cities。可能这张表属于一个遗留的表,我们需要创建一个应用使用这个表在一个我们定义的域模型中,域模型定义了文档不同类型的类层次。或者可能我们创建一个新应用和数据库仅仅是我们想要持久化的数据。

// Database table Document
CREATE TABLE [Documents] (
    [Document_ID] [int] NOT NULL ,
    [Document_Title] [varchar] (32) NULL ,
    [Document_Type] [varchar] (32)  NULL ,
    [Document_PageNumber] [int] NULL  ,
    [Document_City] [varchar] (32)  NULL
)
为了说明这个,让我们看看下面的例子展示了一个继承关系,它的属性可以被持久化到我们的Documents表。首先,我们有一个基本的Document 类,这个类有Id 和properties属性。其次,我们有一个Book类继承了Document类,Book类包含另一叫做PageNumber的属性。最后我们有一个Newspaper类,也继承了Document类包括一个City属性。

// C# class
public class Document
{
  private int _id = -1;
  private string _title = string.Empty;

  public int Id
  {
    get { return _id; }
    set { _id = value; }
  }

  public string Title
  {
    get { return _title; }
    set { _title = value; }
  }
}

public class Book : Document
{
  private int _pageNumber = -1;

  public int PageNumber
  {
    get { return _pageNumber; }
    set { _pageNumber = value; }
  }
}

public class Newspaper : Document
{
  private string _city = string.Empty;

  public string City
  {
    get { return _city; }
    set { _city = value; }
  }
}
现在我们有我们自己的类和数据库表,我们可以开始我们的映射工作了。我们可以创建一个<select>语句,这个语句返回所有的列。为了帮助DataMapper区别不同的文档记录,我们即将表明Document_Type列包含值,这些值将会从区别一条记录和另外一条记录用于映射结果到我们的类层次中。

// Document mapping file
<select id="GetAllDocument" resultMap="document"> 
   select 
     Document_Id, Document_Title, Document_Type,
     Document_PageNumber, Document_City
   from Documents 
   order by Document_Type, Document_Id
</select>

<resultMap id="document" class="Document"> 
  <result property="Id" column="Document_ID"/>
  <result property="Title" column="Document_Title"/>
  <discriminator column="Document_Type" type="string"/>
  <subMap value="Book" resultMapping="book"/>
  <subMap value="Newspaper" resultMapping="newspaper"/>
</resultMap>

<resultMap id="book" class="Book" extends="document"> 
  <property="PageNumber" column="Document_PageNumber"/>
</resultMap>

<resultMap id="newspaper" class="Newspaper"  extends="document"> 
  <property="City" column="Document_City"/>
</resultMap>
DataMapper比较discriminatory列的值到不同的<submap>值通过使用列 值的字符等价。基于这个String值,iBATIS DataMapper将会使用ResultMap命名的"Book"或者“Newspaper”在<submap>中定义的元素或者它会使用父ResultMap“Document”,如果没有一个submap值适合。利用这些resultMaps,我们可以实现面向对象的继承映射到我们的数据库表。

如果你想要使用自定义的,你可以使用<discriminator>元素的typeHandler属性来明确一个自定义类型操作用于区别列。

<alias>
  <typeAlias alias="CustomInheritance" 
  type="IBatisNet.DataMapper.Test.Domain.CustomInheritance, IBatisNet.DataMapper.Test"/>
</alias>

<resultMaps>
  <resultMap id="document-custom-formula" class="Document">
    <result property="Id" column="Document_ID"/>
    <result property="Title" column="Document_Title"/>
    <discriminator column="Document_Type" typeHandler="CustomInheritance"/>
    <subMap value="Book" resultMapping="book"/>
    <subMap value="Newspaper" resultMapping="newspaper"/>
  </resultMap>
</resultMaps>
public class CustomInheritance : ITypeHandlerCallback
{
  #region ITypeHandlerCallback members

  public object ValueOf(string nullValue)
  {
    throw new NotImplementedException();
  }

  public object GetResult(IResultGetter getter)
  {
   string type = getter.Value.ToString();

   if (type=="Monograph" || type=="Book")
   {
     return "Book";
   }
   else if (type=="Tabloid" || type=="Broadsheet" || type=="Newspaper")
   {
     return "Newspaper";
   }
   else
   {
     return "Document";
   }

  }

  public void SetParameter(IParameterSetter setter, object parameter)
  {
   throw new NotImplementedException(); 
  }
  #endregion
}
3.5.7内含的Result Maps

如果列是通过一个SQL语句匹配结果对象返回的,你可能不需要一个内含的Result Map。如果你已经控制了关系数据库,你应该能够命名列,所以他们也可以作为属性名工作。

<statement id="selectProduct" resultClass="Product">
  select
    id,
    description
  from PRODUCT
  where id = #value#
</statement>

另一种方法是跳过一个ResultMap使用列别名来匹配列名和属性名,像下面例子描述的:

<statement id="selectProduct" resultClass="Product">
  select
  PRD_ID as id,
  PRD_DESCRIPTION as description
  from PRODUCT
  where PRD_ID = #value#
</statement>
当然,这些技术不会起作用,如果你不需要明确一个列类型,一个null值,或者任何其他属性。

敏感案例可能也是一个问题利用内含的Result maps。令人信服的,你可以有一个对象有一个“FirstName”属性和一个“Firstname”属性。当iBATIS尝试匹配属性和列时,探索是不敏感的,我们不能保证那个属性将会被匹配。

一个最终的问题是这里会有一些性能当iBATIS自动映射列和属性名称。不同点可能是戏剧性的,如果使用第三方NET数据库提供者。

3.5.8原始Results

很多时候,我们不需要返回一个对象有很多属性,我们仅仅需要一个String,Integer,Boolean等,如果你不需要赋值给一个对象,iBATIS可以返回一个原始类型代替。如果你仅仅需要值,你可以使用一个标准类型作为返回类,像下面例子描述的:

<select id="selectProductCount" resultClass="System.Int32">
  select count(1)
  from PRODUCT
</select>
如果需要,你可以引用一个标准类型使用一个标识符,“Value”,像下面例子描述的:

<resultMap id="select-product-result" resultClass="System.String">
  <result property="value" column="PRD_DESCRIPTION"/>
</resultMap>
3.5.9Maps利用ResultMaps

代替一个对象,有时你应该可能一个简单的键值对数据列表,list中的每一个属性是一个实体。所以,Result Maps可以赋值给IDictionary实例就像属性对象一样简单。使用IDictionary句法标识对象。

<resultMap id="select-product-result" class="HashTable">
  <result property="id" column="PRD_ID"/>
  <result property="code" column="PRD_CODE"/>
  <result property="description" column="PRD_DESCRIPTION"/>
  <result property="suggestedPrice" column="PRD_SUGGESTED_PRICE"/>
</resultMap>

在上面例子中,将会为结果集中的每一行创建一个HashTable的实例,并赋值给Product属性。属性名称的特性,比如,id,code等,将作为实体的键,然后映射的列的值将作为实体的值。

正如上面的例子描述的,你也可以使用一个内含的Result Map。

<statement id="selectProductCount" resultClass="HashTable">
  select * from PRODUCT
</statement>
通过Example xx返回的实体集依赖于结果集中的列。如果列改变了,那么新的实体集也会自动的返回。

注意:确定的供应商可能返回列的名称的大写或者小写。当访问值,比如一个provider,你将会传值给Hashtable或者HashMap的键的名称。

3.5.10复杂属性

在一个关系型数据库,一个表经常引用另一个表。然而,你的一些业务对象可能包括另一个对象或者一个List集合。类型嵌套其他类型叫做“复杂类型”。你可能不想要一个语句返回一个简单的类型,但是一个完全的类型。

在数据库中,一个相关的列通常表示1对1的关系,或者1对多的关系,1对多关系是一个类中包括了其他关系。从数据库返回的列有可能不是我们想要的属性,它只是在另一个查询中被使用的。

从框架的角度看,难题不是加载一个复杂类型,而是加载每一个“复杂的属性”。为了解决这个问题,你可以在Result Map 语句中明确运行中加载一个给定的属性,在下面的例子中,“category”属性是一个复杂的属性。

<resultMaps>
  <resultMap id="select-product-result" class="product">
    <result property="id" column="PRD_ID"/>
    <result property="description" column="PRD_DESCRIPTION"/>
    <result property="category" column="PRD_CAT_ID" select="selectCategory"/>
  </resultMap>

  <resultMap id="select-category-result" class="category">
    <result property="id" column="CAT_ID"/>
    <result property="description" column="CAT_DESCRIPTION"/>
  </resultMap>
</resultMaps>

<statements>
  <select id="selectProduct" parameterClass="int" resultMap="select-product-result">
   select * from PRODUCT where PRD_ID = #value#
  </select>

  <select id="selectCategory" parameterClass="int" resultMap="select-category-result">
   select * from CATEGORY where CAT_ID = #value#
  </select>
</statements>
      
在上面的例子中,框架将会使用"selectCategory"语句给“category”属性赋值。每一个category的值通过“selectCategory”语句赋值的,返回的结果对象赋值给category属性。当处理完成,每一个Product实例将会有一个category对象实例集。

3.5.11避免N+1查询

上面的例子中可能存在一个问题就是无论何时你加载一个Product时,会执行两个查询语句:一个用于查询Produce,另一个用于查询Category。对于一个单独的Product,这种问题是不重要的。但是如果你加载10条Product,那么就会执行11条查询语句。对于100个Products,代替每一个Product语句执行,一共会有101条查询语句。下面的例子总是会执行N+1。

<resultMaps>
  <resultMap id="select-product-result" class="product">
    <result property="id" column="PRD_ID"/>
    <result property="description" column="PRD_DESCRIPTION"/>
    <result property="category" column="PRD_CAT_ID" select="selectCategory"/>
  </resultMap>

  <resultMap id="select-category-result" class="category">
    <result property="id" column="CAT_ID"/>
    <result property="description" column="CAT_DESCRIPTION"/>
  </resultMap>
</resultMaps>

<statements>
  <!-- This statement executes 1 time -->
  <select id="selectProducts" parameterClass="int" resultMap="select-product-result">
   select * from PRODUCT
  </select>

  <!-- This statement executes N times (once for each product returned above) -->
  <select id="selectCategory" parameterClass="int" resultMap="select-category-result">
   select * from CATEGORY where CAT_ID = #value#
  </select>
</statements>
一个方法解决这个问题是缓存“selectCategory”语句。如果我们有100个Products,但是这里应该只有五个categories。代替运行一个SQL查询或者存储过程,框架将会从它的缓存中返回category对象。一个101语句将会执行,但是他们不会访问数据库。

另一个解决方案是使用一个标准的SQL语句加入返回你需要的另一个表的列。一个join可能带来我们需要的所有的列从一个查询语句中。当你有一个嵌套的对象时,你可以引用嵌套的属性通过使用一个.标记,就像"category.description",

例子3.46像上面的例子一样,但是使用join解决了嵌套属性。

<resultMaps>
  <resultMap id="select-product-result" class="product">
    <result property="id" column="PRD_ID"/>
    <result property="description" column="PRD_DESCRIPTION"/>
    <result property="category" resultMapping="Category.CategoryResult" />
  </resultMap>
</resultMaps>

<statements>
  <statement id="selectProduct" parameterClass="int" resultMap="select-product-result">
    select *
    from PRODUCT, CATEGORY
    where PRD_CAT_ID=CAT_ID
    and PRD_ID = #value#
  </statement>
</statements>
延迟加载和Joins对比

最重要的是你应该注意到使用join并不总是比较好的。如果你的情况是很少访问相关的对象,那么它应该避免使用join,加载所有的category属性是无用的。这里尤其重要的是对于数据库设计解决外join或者是空值或者是无索引的列。在这种情况下更好的结果方法是使用子查询解决延迟加载。一般的规则是:使用join,如果你更可能访问相关的属性。然而,只有在延迟加载不是可选时才使用它。

如果你不知道怎么决定使用哪个,那么不需要担心。无论你选择了哪个方法,你可以更改它在不影响你的应用代码的情况下。上面的两个例子返回相同的对象,加载使用准确的相同的方法从一个应用中调用。唯一需要考虑的是,如果你能使用缓存,那么使用分开的查询方法能够导致一个缓存实例返回。但是经常不那样做,那不是问题的原因。(你的应用不应该依赖实例的相等)

3.5.13避免N+1的查询集合(1:M和M:N)

这和1对1的解决方式是相似的,但是更应该关心因为潜在的有更多的数据被调用。上面解决问题的方式是无论什么时候你加载一个Category,两个SQL语句会被运行。这个问题看起来是不重要的当加载一个单独的Category,但是如果你运行一个查询加载10个Categories,一个分开的查询将会执行每一个Category加载它相关的Products集合。这个结果是11个查询,一个用于查询Categories集合,一个用于查询每一个Category加载相关的Products集合(N+1)。为了这种情况更糟,我们处理潜在的更多的数据集合。


<sqlMap namespace="ProductCategory">
<resultMaps>

  <resultMap id="Category-result" class="Category" groupBy="Id">
    <result property="Id" column="CAT_ID"/>
    <result property="Description" column="CAT_DESCRIPTION"/>
    <result property="ProductList" resultMapping="ProductCategory.Product-result"/>
  </resultMap>

  <resultMap id="Product-result" class="Product">
    <result property="Id" column="PRD_ID"/>
    <result property="Description" column="PRD_DESCRIPTION"/>
  </resultMap>
<resultMaps>

<statements>

  <!-- This statement executes 1 time -->
  <statement id="SelectCategory" parameterClass="int" resultMap="Category-result">
    select C.CAT_ID, C.CAT_DESCRIPTION, P.PRD_ID, P.PRD_DESCRIPTION
    from CATEGORY C
    left outer join PRODUCT P
    on C.CAT_ID = P.PRD_CAT_ID
    where CAT_ID = #value#
  </statement>
当你调用
IList myList = sqlMap.QueryForList("SelectCategory", 1002);
主要的查询结果,结果被存储在myList变量中。每一个对象在List中将会有一个“ProductList”属性,它也是一个List集合从相同的查询中,但是使用“Product-Result”映射给子List集合赋值。所以,最终你会有一个List包括一个子List,仅仅只有一个数据库查询被执行。
最重要的条目是:
groupBy="Id"
<result property="ProductList" resultMapping="ProductCategory.Product-result"/>
属性映射在“Category-result”结果映射中。另一个很重要的描述是结果映射ProductList属性是命名空间意识。使用这中方法,你可以解决N+1问题。
延迟加载和joins(1:m和m:n)对比
像前面的描述的1对1解决方案,最重要的注意点是使用join并不总是最好的。这对于集合属性更是如此,它用于特别的属性值由于大的数据量。如果你的情况是很少访问相关的对象,那么它应该避免使用join,加载products集合是不需要的。数据库设计解决了外部join或者null值或者无索引的列。在这些情形下它应该使用子查询和延迟加载更好。一般的规则是:使用join如果你更倾向于访问相关的属性。然而,如果延迟加载不是可选的使用它。
像更早的提到的,如果你不能决定使用哪种方法,不用担心。无论你选择的那个方法,你总是可以在不影响代码的情况下改变。上面的两个例子返回同一个对象图,和使用相同的方法调用加载。唯一需要关心的是,如果你能够缓存,那么使用分离的结果方法,那么是缓存的实例被返回。但是通常不是这样的,但也不会引起问题。
3.5.1.4混合键或者是多个复杂的参数属性
你应该已经注意上面的例子,这里仅仅只有一个单独的键被用于明确的ResultMap中通过列属性。这将建议你仅仅一个单独的列可以关系到一个相关的映射语句。然而,这里有另一个语法允许多个列传值到相关的映射语句。这

Example�3.50.�Mapping a composite key

<resultMaps>
  <resultMap id="select-order-result" class="order">
    <result property="id" column="ORD_ID"/>
    <result property="customerId" column="ORD_CST_ID"/>
    ...
    <result property="payments" column="itemId=ORD_ID, custId=ORD_CST_ID"
      select="selectOrderPayments"/>
  </resultMap>
<resultMaps>

<statements>

  <statement id="selectOrderPayments" resultMap="select-payment-result">
    select * from PAYMENT
    where PAY_ORD_ID = #itemId#
    and PAY_CST_ID = #custId#
  </statement>
</statements>
可选的你可以仅仅明确了列的名称就像他们像参数的顺序一样。例如
ORD_ID, ORD_CST_ID
通常,这是一个简单的性能利用可读性和维护性。
最重要的,现在的IbatIS数据映射框架自动的解决了环形的关系。意识到这个,当实现父/子关系树。一个简单的工作区是简单的定义一个第二个结果Map用于没有加载父对象的情况,或者使用join结果N+1的建议。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值