13590--北极燕鸥


   博采众长,信誉卓著

导航

[原]iBatis.Net(C#)系列三:数据库查询

Posted on 2013-03-14 09:24  北极燕鸥  阅读(5570)  评论(8编辑  收藏  举报

引用请注明http://www.cnblogs.com/13590/archive/2013/03/14/2958735.html 

摘要:查询是数据库SQL语言的核心,本文介绍了通过iBatis.Net对数据库的简单查询、条件查询、动态查询和多表查询。

关键词:iBatis.Net;动态查询;多表查询;数据映射

查询是数据库SQL语言的核心,SQL语言只提供唯一一个用于数据库查询的语句,即SELECT语句。用于表达SQL查询的SELECT语句是功能最强也是最复杂的SQL语句。而在实际的项目开发过程中,查询占了一个很大的比重,通常衡量一个框架的好坏也很大程度上取决于该框架对查询的灵活性和效率。本节介绍在iBatis.Net中提供的数据库查询方式。

在上节建立的项目文件中新添加Maps/Test3.xml和Test3.aspx项,分别记录XML数据映射信息和相应的程序调用信息。

1、简单查询

获取一个表的内容,如获取DEAN.SYSUSER表的用户信息,XML数据映射配置信息为:

<selectid="SelectSysuser"resultMap="SysuserResult">

SELECT * FROM DEAN.SYSUSER

</select>

调用代码为:

protectedvoid Button1_Click(object sender, EventArgs e)

{

//简单查询

try

{

ISqlMapper mapper = Mapper.Instance(); //得到ISqlMapper实例

IList<iBatisTest.Domain.Sysuser> plist = mapper.QueryForList<iBatisTest.Domain.Sysuser>("Test3Map.SelectSysuser",null);//调用QueryForList方法

if (plist != null && plist.Count > 0)

{

GridView1.DataSource = plist;

GridView1.DataBind();

}

Label1.Text = "简单查询成功";

}

catch (Exception ex)

{

Label1.Text = ex.Message;

}

}

这种查询结果返回的是整张表的所有记录,无需传入查询参数,在调用QueryForList方法时把参数置为null。考虑到系统的效率,在实际开发中,对记录数少的小表才使用。

2、条件查询

根据条件来查询结果,条件可以是一个或者多个。这种方式在实际查询中被广泛使用,是应用最多的一种查询。如根据登录用户名查询该用户信息,数据映射配置信息为:

<selectid="SelectSysuserByUserName"parameterClass="string"resultMap="SysuserResult">

SELECT * FROM DEAN.SYSUSER WHERE LOGINNAME=#value#

</select>

iBatis.Net通过使用#或者$符号来占位,即标识参数。在XML数据映射文件的SQL语句中引入参数有2种方式,一种是内联参数方式,即使用parameterClass,如上面的配置就采用这种方式。一种是参数映射方式,使用parameterMap。采用内联方式时,允许我们把属性名称、属性类型、空置方式配置在SQL语句中。

使用内联参数方式时,传入的参数有3种类型,分别为:

(1)基本参数类型,很多SQL语句在查询时只接受一个参数,如int、string,使用#value#来引用,这个value是关键字,不可变,上面的示例就采用这种方式。也可以采用#keyName#来引用,keyName为键名,注意要区分大小写。

(2)字典类型参数,使用IDictionary类型的对象作为参数。通常可以使用Hashtable。如上面配置可修改为

<select id="SelectSysuserByUserName" parameterClass="System.Collections.IDictionary" resultMap="SysuserResult">

SELECT * FROM DEAN.SYSUSER WHERE LOGINNAME=#LOGINNAME#

</select>

(3)对象类型参数,可以是一个类或者是哈希表Hashtable,在使用哈希表时,某一个键值可以是一个列表List类型。

调用代码如下:

protectedvoid Button2_Click(object sender, EventArgs e)

{

//条件查询

try

{

string UserName = TextBox1.Text;//获取查询参数用户名

ISqlMapper mapper = Mapper.Instance(); //得到ISqlMapper实例

IList<iBatisTest.Domain.Sysuser> plist = mapper.QueryForList<iBatisTest.Domain.Sysuser>("Test3Map.SelectSysuserByUserName", UserName);//调用QueryForList方法

if (plist != null && plist.Count > 0)

{

GridView1.DataSource = plist;

GridView1.DataBind();

}

Label1.Text = "条件查询成功";

}

catch (Exception ex)

{

Label1.Text = ex.Message;

}

}

如果采用字典类型参数传入,只需要把调用中的参数修改为:

Hashtable hash = new Hashtable();//声明哈希表

hash.Add("LOGINNAME ", TextBox1.Text); //获取查询参数用户名

3、动态查询

iBatis.Net提供查询的灵活性主要体现在支持动态查询上,即可以动态的生成SQL语句。也只有掌握好动态查询,才能充分的感受iBatis框架所带来的便捷和高效。这也是很多软件公司和开发人员选择该框架的重要原因。

在开发中经常遇到这种查询,当用户没有输入查询条件时查询所有记录,如果用户输入了查询条件将根据查询条件进行查询。比如上面提到的条件查询,如果没有输入用户名信息将返回所有用户信息。这个时候就需要用到动态查询,根据参数值是否为空,生成两条不同的SQL语句。XML数据映射配置信息为:

 

<selectid="SelectSysuserDynamic1"parameterClass="System.Collections.IDictionary"resultMap="SysuserResult">

<![CDATA[ SELECT * FROM DEAN.SYSUSER ]]>

<dynamicprepend="WHERE">

<isNotEmptyprepend="AND"property="LOGINNAME">

<![CDATA[ LOGINNAME = #LOGINNAME# ]]>

</isNotEmpty>

</dynamic>

</select>

dynamic元素用来区分SQL语句的动态部分,dynamic是一个可选项,它中间可以包含任意数据的条件元素。上面的配置信息会根据输入参数LOGINNAME的值是否为空生成两条SQL语句。如果为空SELECT * FROM DEAN.SYSUSER,如果不为空SELECT * FROM DEAN.SYSUSER WHERE LOGINNAME = #LOGINNAME#。

调用程序代码为:

protectedvoid Button3_Click(object sender, EventArgs e)

{

//动态查询1

try

{

Hashtable hash = newHashtable();//声明哈希表

hash.Add("LOGINNAME", TextBox2.Text); //获取查询参数用户名

ISqlMapper mapper = Mapper.Instance(); //得到ISqlMapper实例

IList<iBatisTest.Domain.Sysuser> plist = mapper.QueryForList<iBatisTest.Domain.Sysuser>("Test3Map.SelectSysuserDynamic1", hash);//调用QueryForList方法

if (plist != null && plist.Count > 0)

{

GridView1.DataSource = plist;

GridView1.DataBind();

}

Label1.Text = "动态查询1成功";

}

catch (Exception ex)

{

Label1.Text = ex.Message;

}

}

}

在iBatis.Net中,动态查询的条件元素包含以下几种:二元条件元素、一元条件元素和其他条件元素:

3.1二元条件元素

将一个属性值和静态值或另一个属性值比较,如果条件为真,元素将被包容在查询SQL语句中。

二元条件元素的属性:

perpend——可被覆盖的SQL语句组成部分,添加在语句的前面,该属性为可选。

property——是比较的属性,该属性为必选。

compareProperty——另一个用于和前者比较的属性(必选或选择compareValue属性)

compareValue——用于比较的值(必选或选择compareProperty属性)

二元条件元素为:

<isEqual>

比较属性值和静态值或另一个属性值是否相等,如果相等则查询条件有效。如:

<isEqual prepend="AND" property="status" compareValue="Y">

MARRIED = 'TRUE'

</isEqual>

<isNotEqual>

比较属性值和静态值或另一个属性值是否不相等,如果不相等则查询条件有效。

<isGreaterThan>

比较属性值是否大于静态值或另一个属性值,如果大于则查询条件有效。如:

<isGreaterThan prepend="AND" property="age" compareValue="18">

ADOLESCENT = 'FALSE'

</isGreaterThan>

<isGreaterEqual>

比较属性值是否大于等于静态值或另一个属性值,如果相等等于则查询条件有效。

<isLessThan>

比较属性值是否小于静态值或另一个属性值,如果小于则查询条件有效。

<isLessEqual>

比较属性值是否小于等于静态值或另一个属性值。如:

<isLessEqual prepend="AND" property="age"              compareValue="18">
ADOLESCENT = 'TRUE'
</isLessEqual>

二元条件元素多用在数字的区间选择上,如年龄、价格、面积等选择上面,也可以用在日期、字符串等类型的比较。如只显示ID<=10的指定ID的用户信息,如果输入值大于10则显示全部用户信息。XML数据映射配置信息为:

<selectid="SelectSysuserDynamic2"parameterClass="System.Collections.IDictionary"resultMap="SysuserResult">

<![CDATA[ SELECT * FROM DEAN.SYSUSER ]]>

<dynamicprepend="WHERE">

<isLessEqualprepend="AND"property="USERID"compareValue="10">

USERID = #USERID#

</isLessEqual>

</dynamic>

</select>

调用代码为:

protectedvoid Button4_Click(object sender, EventArgs e)

{

//动态查询2:二元条件元素查询

try

{

Hashtable hash = newHashtable();//声明哈希表

int ID = 0;

if (!string.IsNullOrWhiteSpace(TextBox3.Text))

{

ID = Convert.ToInt32(TextBox3.Text);

}

hash.Add("USERID", ID); //获取查询参数

ISqlMapper mapper = Mapper.Instance(); //得到ISqlMapper实例

IList<iBatisTest.Domain.Sysuser> plist = mapper.QueryForList<iBatisTest.Domain.Sysuser>("Test3Map.SelectSysuserDynamic2", hash);//调用QueryForList方法

if (plist != null && plist.Count > 0)

{

GridView1.DataSource = plist;

GridView1.DataBind();

}

else

{

GridView1.DataSource = null;

GridView1.DataBind();

}

Label1.Text = "动态查询2成功";

}

catch (Exception ex)

{

Label1.Text = ex.Message;

}

}

3.2一元条件元素

一元条件元素检查属性的状态是否符合特定的条件。即检查属性值是否满足条件,如果满足则查询条件有效。

一元条件元素的属性和二元条件元素一样,具有prepend和property属性,其中property为必选属性。

一元条件元素为:

<isPropertyAvailable>

检查是否存在该属性。

<isNotPropertyAvailable>

检查是否不存在该属性。

<isNull>

检查属性是否为null。

<isNotNull>

检查属性是否不为null。

<isEmpty>

检查属性是否为空,属性的数据类型为Collection、String 时检查是否为null或空,即是否为""或size() < 1。如:

<isNotEmpty prepend="AND" property="firstName" >

FIRST_NAME=#firstName#

</isNotEmpty>

<isNotEmpty>

检查属性是否不为空,检查方式同上。

比如下面的配置例子:

<selectid="SelectSysuserDynamic3"resultMap="SysuserResult"parameterClass="System.Collections.IDictionary">

<![CDATA[ SELECT * FROM DEAN.SYSUSER ]]>

<dynamicprepend="WHERE">

<isPropertyAvailableproperty="SEX">

<isNotNullproperty="SEX"prepend="AND">

SEX=#SEX#

</isNotNull>

</isPropertyAvailable>

<isPropertyAvailableproperty="STATUS">

<isNotNullproperty="STATUS"prepend="AND">

STATUS=#STATUS#

</isNotNull>

</isPropertyAvailable>

</dynamic>

</select>

先判断传入参数集是否有SEX参数,如果没有则不执行SEX=#SEX#查询条件,再判断该参数是否为null,不为null才执行查询条件。isPropertyAvailable元素最大的好处是,如果输入的参数集不包括设置的参数时程序不会报错,直接跳过该元素设置内容。

3.3其他元素条件

其他元素条件有两个元素,一个为ParameterPresent,该元素检查参数对象是否存在,一个为Iterate,该元素为遍历整个集合。

(1) ParameterPresent

ParameterPresent元素属性只有prepend一个属性,表示可被覆盖的SQL语句组成部分,添加在语句的前面,为可选属性。

<isParameterPresent>

检查是否存在参数对象,即如果参数类不为NULL则查询条件有效。如:

<isParameterPresent prepend="AND">
  EMPLOYEE_TYPE = #empType#
</isParameterPresent>

<isNotParameterPresent>

检查是否不存在参数对象,如:

<isNotParameterPresent prepend="AND">
  EMPLOYEE_TYPE = 'DEFAULT'
</isNotParameterPresent>

(2) Iterate:遍历整个集合元素,为List集合中的元素重复元素体的内容。

Iterate的属性:

prepend——可被覆盖的SQL语句组成部分,添加在语句的前面,该属性为可选。

property——类型为List的用于遍历的元素属性,该属性为必选。

open——整个遍历内容体开始的字符串,用于定义括号,该属性为可选。

close ——整个遍历内容体结束的字符串,用于定义括号,该属性为可选。

conjunction——每次遍历内容之间的字符串,用于定义AND或OR,该属性为可选。

<iterate>

遍历类型为List的元素。如:

<iterate prepend="AND" property="UserNameList"

open="(" close=")" conjunction="OR">

username=#UserNameList[]#

</iterate>

注意:使用<iterate>时,在List元素名后面包括方括号[]非常重要,方括号[]将对象标记为List,以防解析器简单地将List输出成String。

Iterate元素在生成sql语句时,标签中的内容是循环生成的,如上面的例子将会生成语句:(username=xxx1 or username=xxx2 or username=xxx 3)。该元素也经常用来动态生成In查询条件,如id in (xx1,xx2,xx3,.....),括号中的(包括括号)都由该元素标签生成。

比如下面的配置例子:

<selectid="SelectSysuserDynamic4"resultMap="SysuserResult"parameterClass="System.Collections.IDictionary">

<![CDATA[ SELECT * FROM DEAN.SYSUSER ]]>

<dynamicprepend=" WHERE">

<isPropertyAvailableproperty="SEX">

<isNotNullproperty="SEX"prepend="AND">

SEX=#SEX#

</isNotNull>

</isPropertyAvailable>

<isNotNullprepend="And"property="USERIDLIST">

USERID in

<iterateproperty="USERIDLIST"open="("close=")"conjunction=",">

#USERIDLIST[]#

</iterate>

</isNotNull>

</dynamic>

</select>

调用代码为:

protectedvoid Button6_Click(object sender, EventArgs e)

{

//动态查询2:其它元素条件,Iterate

try

{

Hashtable hash = newHashtable();//声明哈希表

string sex = "";

hash.Add("SEX", sex);

List<int> IDS = newList<int>();//声明List对象

IDS.Add(1);

IDS.Add(2);

IDS.Add(3);

hash.Add("USERIDLIST", IDS);

ISqlMapper mapper = Mapper.Instance(); //得到ISqlMapper实例

IList<iBatisTest.Domain.Sysuser> plist = mapper.QueryForList<iBatisTest.Domain.Sysuser>("Test3Map.SelectSysuserDynamic4", hash);//调用QueryForList方法

if (plist != null && plist.Count > 0)

{

GridView1.DataSource = plist;

GridView1.DataBind();

}

else

{

GridView1.DataSource = null;

GridView1.DataBind();

}

Label1.Text = "动态查询4成功";

}

catch (Exception ex)

{

Label1.Text = ex.Message;

}

}

系统会根据配置信息动态的生成In查询条件,最终动态生成的SQL语句为:SELECT * FROM DEAN.SYSUSER AND SEX='男' And USERID in (1,2,3)。

4、多表查询

前面讲到的示例都是从一个表中查询记录,获取的结果也是单个对象。事实上在程序开发中,经常需要对多个表进行组合查询,返回的结果也是复杂对象。如查询用户权限信息,就需要关联用户表和权限表。

向数据库添加系统权限表SysUserRight,脚本如下:

CREATE TABLE DEAN.SYSUSERRIGHT

(

ID NUMBER(10,0) NOT NULL ENABLE,

USERID NUMBER(10,0) NOT NULL ENABLE,

RIGHTID NUMBER(10,0) NOT NULL ENABLE,

constraint PK_SYSUSERRIGHT primary key (ID)

);

comment on column DEAN.SYSUSERRIGHT.ID is 'ID';

comment on column DEAN.SYSUSERRIGHT.USERID is '用户ID';

comment on column DEAN.SYSUSERRIGHT.RIGHTID is '权限ID';

有两种方式来处理这种多表查询,一种是参照单表查询,根据返回结果定制一个新类,或者直接设置返回参数为Hashtable表。如:

<selectid="MultiTable1"resultClass="Hashtable" >

SELECT A.*,B.RIGHTID FROM DEAN.SYSUSER A,DEAN.SYSUSERRIGHT B WHERE A.USERID=B.USERID

</select>

通过用户ID(USERID)关联用户表(SYSUSER)和系统权限表(SYSUSERRIGHT)查询出用户信息及对应的权限信息,直接返回一个Hashtable表,记录了相应的信息。

第二种方式是利用iBatis的复杂属性来实现,在Sysuser类新增一个属性:

///<summary>

///多表查询新增权限属性

///</summary>

privateint _rightid;

publicint Rightid

{

get { return _rightid; }

set {_rightid = value;}

}

修改配置文件信息,在resultMaps部分增加一个结果映射信息,唯一号为UserRightResult,它继承于Test3Map.SysuserResult结果映射,增加的配置信息如下:

<resultMapid="UserRightResult"class="Sysuser"extends="Test3Map.SysuserResult">

<resultproperty="<spansstyle="color:blue; background-color:white">Rightid"column="USERID=USERID"select="Test3Map.SelectSysuserRight" />

</resultMap>

在statements节加入如下信息:

<statementid="SelectSysuserRight"parameterClass="int"resultClass="int">

SELECT RIGHTID FROM DEAN.SYSUSERRIGHT WHERE USERID= #USERID#

</statement>

<selectid="MultiTable2"parameterClass="int"resultMap="UserRightResult">

SELECT * FROM DEAN.SYSUSER ORDER BY USERID

</select>

通过XML配置文件中resultMap的result使用"select"进行一种迭代查询,也就是将<result property="Rightid" column="USERID=USERID" select="Test3Map.SelectSysuserRight" />中column指定的一项或多项作为参数(USERID=USERID),传入并执行指定的select语句SelectSysuserRight,并将查询结果赋给property=" Rightid ",从而实现多表查询。

该例子中实现的是1:1的关系查询,如果是1:n的关系查询,只需要Sysuser类增加的属性修改为IList类型。Statements节点"SelectSysuserRight"的返回类修改为resultClass=" SysuserRightResult"。

通过iBatis复杂属性,可以非常方便的实现多表查询,但这个方法给我们带来便利的同时,也带来了两个问题。首先,创建包含大量对象的列表可能会消耗大量的内存。其次,这种方法会导致数据库的I/O问题,其原因就是所谓的"N+1"查询现象。对于主从表(也称为父子表)的查询,特别容易产生N+1查询问题,N+1查询问题是由于试图加载多个父记录(比如User)的子记录(Right)而引起的。在查询父记录时,只需要1条语句,假设返回N条记录,那么就需要再执行N条语句来查询子记录,引发所谓的"N+1查询"。

解决N+1查询问题可以通过延迟加载(lazy loading)来实现,它将加载过程打散为一些更小的过程。在父子表查询过程中,对子表的查询往往不需要和父表一起加载,例如,系统的用户管理,打开一个用户信息页面时显示的是用户信息列表,当点击一个用户时,才需要加载该用户的权限信息。这种情况就特别适合使用延迟加载,每次都仅查询一个列表。使用延迟加载的时候还需要特别注意,使用的动态代理的对象的所有方法和属性都必须是virtual类型。

要实现延迟加载,只需要在配置文件里面加入lazyLoad="true"属性就可以了。通过延迟加载,它能提高查询的效率,但并没有真正解决数据库I/O问题,在最坏的情况下,它对数据库的访问次数与非延迟加载的时候是一样的。如何真正解决N+1查询问题呢?iBatis提供了连接语句(join)方式来完全避免N+1查询的出现。

修改配置文件信息,在resultMaps部分增加如下配置:

<resultMapid="SysuserRightResult"class="SysuserRight">

<resultproperty="ID"column="ID"/>

<resultproperty="Userid"column="USERID" />

<resultproperty="Rightid"column="RIGHTID"/>

</resultMap>

<resultMapid="SysuserJoinResult"class="SysuserJoin"extends="Test3Map.SysuserResult"groupBy="Userid">

<resultproperty="RightList"resultMapping="Test3Map.SysuserRightResult" />

</resultMap>

这个配置使用了relultMap的resultMapping属性,该属性用在如果一个数据类的属性本身不是基元数据类型,而是一个复杂数据类型的场景。这个时候就不能用一个简单的result元素来表示,必须给他一个完整的resultMap。resultMapping的值指明RightList属性由结果映射集SysuserRightResult所表示的复杂数据类型表示。因为用户和权限信息是一对多的关系,在主表的结果映射上加入groupBy="Userid"属性。

注意用户类SysuserJoin的权限属性RightList的定义,它是由权限类SysuserRight组成的一个列表。RightList定义如下:

///多表查询新增权限列表属性

privateIList<SysuserRight> _rightlist;

publicIList <SysuserRight> RightList

{

get { return _rightlist; }

set { _rightlist = value; }

}

在statements节加入如下信息

<selectid="MultiTable3"resultMap="SysuserJoinResult" >

select A.*,B.* FROM DEAN.SYSUSER A LEFT JOIN DEAN.SYSUSERRIGHT B ON A.USERID=B.USERID </select>

再通过程序的调用,就不会出现N+1查询问题。

5、结束语

以上程序在VS2012(C#)+Oracle11gR2+Win7(64位)调试通过,附件的例子程序中有详细的配置信息和程序调用代码。