NHibernate中如何使用Criteria、HQL、Native SQL和命名查询进行查询?
- 内容介绍
- 文章标签
- 相关推荐
本文共计2853个文字,预计阅读时间需要12分钟。
首先,对PlantItem对象的数据库结构进行一些调整,然后进行本节中的测试。在NHibernate测试系列04的一节中,测试对象PlantItem是一种复合主键对象,使用一个具有语义上的ID进行比较合适。这里,我们采用以下步骤:
1. 调整PlantItem的数据库结构,使其包含复合主键。
2.修改NHibernate的映射文件,以反映新的主键结构。
3.编写测试用例,确保PlantItem的复合主键比较逻辑正确。
首先,把PlantItem对象的数据结构做一些调整,然后再进行本节中的测试。在NHibernate考察系列 04一节中测试结果,象PlantItem这种复合主键对象,使用一个语意上的ID比较合适,这里我们就按照这种方式修改过来。因为domain对ID属性没有任何依赖,不用于对象间的关联,因此使用一个整数类型就可以了。为TBLPLANTITEM表添加一个int的ID字段,设置成identity(最好为ID字段添加一个非聚集索引)。类和映射文件大致如下:
publicintID
{
get{return_id;}
set{_id=value;}
}
privateint_id;
publicvirtualPlantPlant
{
get{return_plant;}
set{_plant=value;}
}
privatePlant_plant;
publicvirtualItemItem
{
get{return_item;}
set{_item=value;}
}
privateItem_item;
<idname="ID">
<columnname="ID"sql-type="int"/>
<generatorclass="native"/>
</id>
<many-to-onename="Plant"class="Plant"column="PLANT_ID"lazy="proxy"/>
<many-to-onename="Item"class="Item"column="ITEM_ID"lazy="proxy"/> 用下面的测试代码加入测试数据:
Companycompany=newCompany("1000","testcompany1","",newHashedSet<Plant>());
session.Save(company);
Plantplant1=newPlant("1105","testplant1",company);
session.Save(plant1);
Plantplant2=newPlant("1202","testplant2",company);
session.Save(plant2);
Itemitem1=newItem("FK1.1023.78AF","2.5#LCD","PCS",newdecimal(85.7));
session.Save(item1);
Itemitem2=newItem("191.1023.78AF","testitem2","PCS",newdecimal(12));
session.Save(item2);
Itemitem3=newItem("023.0000.1233","testitem3","PCS",newdecimal(25.7));
session.Save(item3);
Itemitem4=newItem("FK1.2314.ZF31","testitem4","PCS",newdecimal(1.789));
session.Save(item4);
Itemitem5=newItem("100000000070","testitem5","EA",newdecimal(19));
session.Save(item5);
Itemitem6=newItem("ANTXX00230GD","testitem6","EA",newdecimal(19));
session.Save(item6);
//创建PlantItem对象
session.Save(newPlantItem(plant1,item1,"PCS",ItemCategoryEnum.P,PurchaseCategoryEnum.JIT,StockOptionEnum.ERP));
session.Save(newPlantItem(plant1,item2,"PCS",ItemCategoryEnum.P,PurchaseCategoryEnum.PO,StockOptionEnum.None));
session.Save(newPlantItem(plant1,item3,"PCS",ItemCategoryEnum.P,PurchaseCategoryEnum.PO,StockOptionEnum.ERP));
session.Save(newPlantItem(plant1,item4,"PCS",ItemCategoryEnum.M,PurchaseCategoryEnum.JIT,StockOptionEnum.Hub));
session.Save(newPlantItem(plant1,item5,"PCS1",ItemCategoryEnum.M,PurchaseCategoryEnum.JIT,StockOptionEnum.None));
session.Save(newPlantItem(plant1,item6,"EA",ItemCategoryEnum.M,PurchaseCategoryEnum.PO,StockOptionEnum.None));
session.Save(newPlantItem(plant2,item2,"PCS",ItemCategoryEnum.M,PurchaseCategoryEnum.JIT,StockOptionEnum.ERP));
session.Save(newPlantItem(plant2,item3,"PCS",ItemCategoryEnum.M,PurchaseCategoryEnum.PO,StockOptionEnum.Hub));
session.Save(newPlantItem(plant2,item4,"PCS",ItemCategoryEnum.P,PurchaseCategoryEnum.PO,StockOptionEnum.Hub));
session.Save(newPlantItem(plant2,item5,"PCS",ItemCategoryEnum.M,PurchaseCategoryEnum.PO,StockOptionEnum.Hub));
1. Criteria 条件式查询
ICriteriacriteria=session.CreateCriteria(typeof(PlantItem));
criteria.Add(Expression.Eq("PurchaseCategory",PurchaseCategoryEnum.PO))
.AddOrder(Order.Desc("Item"))
.SetFirstResult(2).SetMaxResults(2);
IList<PlantItem>list=criteria.List<PlantItem>();
for(inti=0;i<list.Count;i++)
{
Console.WriteLine("{0}\t{1}\t\t{2}",
list[i].Plant.PlantID,list[i].Item.ItemID,list[i].PurchaseCategory.ToString());
} SQL语句:
execsp_executesqlN'
SELECTtop4this_.IDasID2_0_,this_.PLANT_IDasPLANT2_2_0_,this_.ITEM_IDasITEM3_2_0_,
this_.UNITasUNIT2_0_,this_.ITEM_CATEGORYasITEM5_2_0_,this_.PURCHASE_CATEGORYasPURCHASE6_2_0_,
this_.STOCK_OPTIONasSTOCK7_2_0_,this_.CREATE_DATEasCREATE8_2_0_,this_.CREATE_TIMEasCREATE9_2_0_
FROMTBLPLANTITEMthis_WHEREthis_.PURCHASE_CATEGORY=@p0ORDERBYthis_.ITEM_IDdesc',
N'@p0nvarchar(2)',@p0=N'PO' 条件式查询提供like、in、between等各种方式查询,基本可以满足应用的要求。
条件式查询以及HQL都是在对象层面进行query,因此提供的参数都是对象属性名称、属性值。例如上面的AddOrder语句,是要求结果集按照PlantItem对象的Item属性降序排列,NHibernate根据映射信息生成SQL,在SQL中使用的是ORDER BY ITEM_ID desc实现。
SetFirstResult、SetMaxResults可以用于实现分页,不同的数据库有不同的实现方式,例如SQL Server 2000使用的是上面取top 4的方式,然后在程序中过滤掉FirstResult之前的对象。
如果映射中配置了对象关联,可以在条件式查询中使用关联进行查询:
ICriteriacriteria=session.CreateCriteria(typeof(PlantItem))
.Add(Expression.Eq("PurchaseCategory",PurchaseCategoryEnum.PO));
ICriteriaitemCriteria=criteria.CreateCriteria("Item")
.Add(Expression.Like("ItemDescription","%testitem%"))
.AddOrder(Order.Desc("ItemDescription"));
IList<PlantItem>list=criteria.List<PlantItem>();
for(inti=0;i<list.Count;i++)
{
Console.WriteLine("{0}\t{1}\t\t{2}\t{3}",
list[i].Plant.PlantID,list[i].Item.ItemID,list[i].PurchaseCategory.ToString(),list[i].Item.ItemDescription);
} SQL语句:
execsp_executesqlN'
SELECTthis_.IDasID2_1_,this_.PLANT_IDasPLANT2_2_1_,this_.ITEM_IDasITEM3_2_1_,this_.UNITasUNIT2_1_,
this_.ITEM_CATEGORYasITEM5_2_1_,this_.PURCHASE_CATEGORYasPURCHASE6_2_1_,
this_.STOCK_OPTIONasSTOCK7_2_1_,this_.CREATE_DATEasCREATE8_2_1_,this_.CREATE_TIMEasCREATE9_2_1_,
item1_.ITEM_IDasITEM1_7_0_,item1_.ITEM_DESCRIPTIONasITEM2_7_0_,item1_.UNITasUNIT7_0_,
item1_.PRICEasPRICE7_0_
FROMTBLPLANTITEMthis_innerjoinTBLITEMitem1_onthis_.ITEM_ID=item1_.ITEM_ID
WHEREthis_.PURCHASE_CATEGORY=@p0anditem1_.ITEM_DESCRIPTIONlike@p1
ORDERBYitem1_.ITEM_DESCRIPTIONdesc',
N'@p0nvarchar(2),@p1nvarchar(11)',
@p0=N'PO',@p1=N'%testitem%' 上面的语句查询PlantItem集合对象,对PurchaseCategory下条件,也对PlantItem.Item.ItemDescription属性下条件,并要求按照PlantItem.Item.ItemDescription降序排列。NHibernate根据映射配置文件中PlantItem类与Item类之间的many-to-one关系,自动将TBLPLANTITEM与TBLITEM进行inner join查询,然后对ITEM_DESCRIPTION字段下条件,按照ITEM_DESCRIPTION排序。
另外一种条件式查询方式,是给出一个example对象,NHibernate根据传入的example对象属性是否有赋值,确定是否应该对该属性生成一个查询条件。
2. HQL
HQL语法上类似SQL,也不同于SQL。SQL面向的是数据库的database schema/data structure,HQL面向的是对象模型。
简单用法如下:
Plantplant=session.Get<Plant>("1105");
stringhql="fromNH12.MyExample.Domain.PlantItemas pi where pi.Item.ItemID=:ItemIDand pi.Plant=:Plant";
IQueryquery=session.CreateQuery(hql)
.SetString("ItemID","191.1023.78AF")
.SetEntity("Plant",plant);
IList<PlantItem>list=query.List<PlantItem>(); 首先,整个hql语句中使用的都是对象、对象属性,跟数据库table没有关系。
上面的hql要查询的是PlantItem对象列表,因为是基于对象模型,映射配置文件中PlantItem、Plant、Item对象之间的关联关系已经配置,因此在hql中可以使用这些对象以及它们之间已经建立的关联关系。既可以象ItemID一样使用Item对象的属性进行查询,也可以象Plant一样,对整个Plant属性下查询条件。SQL如下:
execsp_executesqlN'
selectplantitem0_.IDasID2_,plantitem0_.PLANT_IDasPLANT2_2_,plantitem0_.ITEM_IDasITEM3_2_,
plantitem0_.UNITasUNIT2_,plantitem0_.ITEM_CATEGORYasITEM5_2_,
plantitem0_.PURCHASE_CATEGORYasPURCHASE6_2_,plantitem0_.STOCK_OPTIONasSTOCK7_2_,
plantitem0_.CREATE_DATEasCREATE8_2_,plantitem0_.CREATE_TIMEasCREATE9_2_
fromTBLPLANTITEMplantitem0_
where(plantitem0_.ITEM_ID=@p0)and(plantitem0_.PLANT_ID=@p1)',
N'@p0nvarchar(13),@p1nvarchar(4)',
@p0=N'191.1023.78AF',@p1=N'1105' 如果是对整个对象下查询条件,NHibernate根据关联的ID生成SQL条件;如果对关联对象仅仅使用到关联用的ID字段,NHB会比较智能,不会使用join。例如上面的例子,虽然对PlantItem.Item.ItemID加了一个查询条件,但NHB并没有对这两个表使用join子句。如果上面的例子中添加一个查询条件:pi.Item.ItemDescription like :ItemDesc,这样NHB就必须要将TBLPLANTITEM和TBLITEM表join进行查询了。
hql也可以象SQL多个表join一样,对于那些在映射配置文件中并没有建立的关联关系,可以在hql中使用join。例如:
fromPlantItemaspi,Userusrwherepi.Plant.PlantID=usr.UserID
接下来我对一件事情比较感兴趣。在NHibernate考察系列 04中,PlantItem对象加了个CreateTime属性,这个属性有点特殊,在对象中使用的是一个DateTime类型的属性,而映射到数据库却是将日期和时间分别转化成字符串保存在数据库的CREATE_DATE、CREATE_TIME两个字段中。这种情况下,如果需要在hql中对这个属性下查询条件,情况会怎么样?
stringhql="fromNH12.MyExample.Domain.PlantItemaspiwherepi.CreateTime>:Time";
IQueryquery=session.CreateQuery(hql)
.SetParameter("Time",DateTime.Parse("2007-04-1416:01:01"));
IList<PlantItem>list=query.List<PlantItem>(); 测试结果,NHibernate不支持,异常信息为:path expression ends in a composite value。Hibernate能够实现这一特性将会是一件非常好的事情,但要实现这一特性不简单。以上面的例子来看,Hibernate怎样根据CreateTime>:Time这一条件生成SQL?((CREATE_DATE=@date AND CREATE_TIME>@time) OR CREATE_DATE>@date)?在不同的运用场景下情况会更复杂。
这里对上面这种情形的用户自定义类型带来相当的限制,因此慎用。
详细的hql语法参考NHibernate的文档。
个人对条件式查询、hql的看法:
1. 实现多数据库兼容。
2. 对持久化存取知识的封装。可能比较直观的想法是既然做到了多数据库兼容,数据库知识的封装作用已经不大。一方面,对象属性与持久化存储之间有差异,使用hql使得这种差异被封装在映射文件或者自定义的映射类型中。另一方面,在粗粒度对象设计的情况下,实体跟表之间基本一一对应,在对象模型层面写hql跟在table层面写SQL的确没有太多差异。但如果使用细粒度对象设计,对象模型与table之间的差异就会相当明显,domain既要处理映射行为,控制数据存取,又要处理复杂的对象关系,情况就复杂化了。分离一个DAL出来,成本代价也是比较高。
3. 性能问题。
这个问题就有点复杂化了。使用ORM之后,在应用与数据库之间建立一个隔离带,性能问题不会消失。解决性能问题有两种倾向,第一种是类似iBATIS做法,把存取数据用的SQL做分离,集中管理,方便数据库、SQL层面做优化。另一种做法是结合domain的架构设计来解决性能问题。
NHibernate的应用产生性能问题,一方面是不合理的对象关系,在数据加载方面造成浪费、开销。另一方面是不合理的条件式查询、hql的使用。确保良好的对象模型设计,根据设计思想,在条件式查询、hql的使用上制定约束、规范变得很重要。
3. Named Query
Named Query是将hql从代码中分离出来,以命名的形式放入配置文件中。这跟iBATIS的方式有点类似,便于对hql的集中管理和维护。
在PlantItem.hbm.xml配置文件的hibernate-mapping元素下添加下面的Named Query hql语句:
<queryname="NH12.MyExample.Domain.PlantItemQuery">
<![CDATA[
fromNH12.MyExample.Domain.PlantItemaspi
wherepi.Item.ItemDescriptionlike:ItemDesc
orderbypi.Plant,pi.Item
]]>
</query> 程序中使用:
IList<PlantItem>list=session.GetNamedQuery("NH12.MyExample.Domain.PlantItemQuery")
.SetString("ItemDesc","testitem5%")
.List<PlantItem>();
4. Native SQL
Native SQL提供直接对数据库访问的机会。
stringsql=@"
selectpi.PLANT_IDasPlantID,pi.ITEM_IDasItemID,i.ITEM_DESCRIPTIONasItemDescription
fromTBLPLANTITEMpi
innerjoinTBLITEMioni.ITEM_ID=pi.ITEM_ID
orderbypi.PLANT_ID,i.ITEM_DESCRIPTION
";
ISQLQueryquery=session.CreateSQLQuery(sql)
.AddScalar("PlantID",NHibernateUtil.String)
.AddScalar("ItemID",NHibernateUtil.String)
.AddScalar("ItemDescription",NHibernateUtil.String);
IListlist=query.List(); 返回的是一个object数组的列表。
用下面的方法返回实体对象列表:
stringsql=@"select*fromTBLPLANTITEM";
ISQLQueryquery=session.CreateSQLQuery(sql).AddEntity(typeof(PlantItem));
IList<PlantItem>list=query.List<PlantItem>();
用下面的方法可以一次返回多个对象列表:
stringsql=@"select{pi.*},{i.*}fromTBLPLANTITEMpi,TBLITEMiwherepi.ITEM_ID=i.ITEM_ID";
ISQLQueryquery=session.CreateSQLQuery(sql)
.AddEntity("pi",typeof(PlantItem))
.AddEntity("i",typeof(Item));
IListlist=query.List();
for(inti=0;i<list.Count;i++)
{
object[]collections=list[i]asobject[];
PlantItempi=collections[0]asPlantItem;
Itemitem=collections[1]asItem;
Console.WriteLine("{0},\t{1},\t{2},\t{3}",pi.Plant.PlantID,pi.Item.ItemID,pi.Item.ItemDescription,pi.Plant.PlantName);
Console.WriteLine("{0},\t{1}",item.ItemID,item.ItemDescription);
} 首先注意SQL语句,对需要选择的列必须使用别名,因为两个表中有一个名称相同的字段ITEM_ID。SQL语句中的{pi.*},{i.*}不是SQL语法,而是告诉NHibernate对pi.*和i.*中的所有列都使用别名,以区别选出的每一个字段是属于哪个对象的。数据库执行的SQL如下,可以看到跟我们在代码中给出的SQL已经不一样了,主要是NHB在中间进行了处理,为每个列生成了别名:
selectpi.IDasID2_0_,pi.PLANT_IDasPLANT2_2_0_,pi.ITEM_IDasITEM3_2_0_,pi.UNITasUNIT2_0_,
pi.ITEM_CATEGORYasITEM5_2_0_,pi.PURCHASE_CATEGORYasPURCHASE6_2_0_,
pi.STOCK_OPTIONasSTOCK7_2_0_,pi.CREATE_DATEasCREATE8_2_0_,pi.CREATE_TIMEasCREATE9_2_0_,
i.ITEM_IDasITEM1_7_1_,i.ITEM_DESCRIPTIONasITEM2_7_1_,i.UNITasUNIT7_1_,i.PRICEasPRICE7_1_
fromTBLPLANTITEMpi,TBLITEMi
wherepi.ITEM_ID=i.ITEM_ID 返回的IList对象是一个object[]类型,第一个元素是PlantItem对象,第二个元素是Item对象。
单步调试上面的测试代码,并监控SQL语句,可以发现另外一个小区别:访问pi.Plant.PlantName可能会产生一个SQL查询,因为相关的Plant对象可能还没有被缓存到session中;而任何时候访问pi.Item.ItemDescription,都不会再产生SQL查询,因为在执行IListlist=query.List()时相关Item对象已经一起返回,并被session缓存过了。
上面这些都是根据NHibernate的文档写的测试代码,下面的一些功能特性在文档中都讲述的比较详细,也就不再测试了。
Named SQL Query,跟NamedQuery差不多,但是需要提供另外的一些信息,例如返回的各个字段Type是什么,是否返回某一个实体对象,如果是,返回的结果集中各个字段跟实体属性的对应关系等。
可以使用存储过程,跟Named SQL Query一样,需要对返回的结果集进行额外的一些配置说明。如果存储过程返回多个DataTable(SQL Server、MySQL等),NHB只取第一个DataTable。
可以为实体的insert、update、delete使用自定义的SQL语句,可以为实体的load使用hql或者是SQL的Named Query,可以在这些里面使用存储过程。
Native SQL的支持,包括实体的insert、update、delete、load中对SQL的支持,是Hibernate留给用户解决复杂问题的最后手段了。使用Native SQL解决问题的灵活度相当大,也能使你的应用架构很好的跟NHibernate整合在一起,因此适当的使用Native SQL、NHibernate提供的扩展,在设计上会拥有相当不错的发挥空间。
本文共计2853个文字,预计阅读时间需要12分钟。
首先,对PlantItem对象的数据库结构进行一些调整,然后进行本节中的测试。在NHibernate测试系列04的一节中,测试对象PlantItem是一种复合主键对象,使用一个具有语义上的ID进行比较合适。这里,我们采用以下步骤:
1. 调整PlantItem的数据库结构,使其包含复合主键。
2.修改NHibernate的映射文件,以反映新的主键结构。
3.编写测试用例,确保PlantItem的复合主键比较逻辑正确。
首先,把PlantItem对象的数据结构做一些调整,然后再进行本节中的测试。在NHibernate考察系列 04一节中测试结果,象PlantItem这种复合主键对象,使用一个语意上的ID比较合适,这里我们就按照这种方式修改过来。因为domain对ID属性没有任何依赖,不用于对象间的关联,因此使用一个整数类型就可以了。为TBLPLANTITEM表添加一个int的ID字段,设置成identity(最好为ID字段添加一个非聚集索引)。类和映射文件大致如下:
publicintID
{
get{return_id;}
set{_id=value;}
}
privateint_id;
publicvirtualPlantPlant
{
get{return_plant;}
set{_plant=value;}
}
privatePlant_plant;
publicvirtualItemItem
{
get{return_item;}
set{_item=value;}
}
privateItem_item;
<idname="ID">
<columnname="ID"sql-type="int"/>
<generatorclass="native"/>
</id>
<many-to-onename="Plant"class="Plant"column="PLANT_ID"lazy="proxy"/>
<many-to-onename="Item"class="Item"column="ITEM_ID"lazy="proxy"/> 用下面的测试代码加入测试数据:
Companycompany=newCompany("1000","testcompany1","",newHashedSet<Plant>());
session.Save(company);
Plantplant1=newPlant("1105","testplant1",company);
session.Save(plant1);
Plantplant2=newPlant("1202","testplant2",company);
session.Save(plant2);
Itemitem1=newItem("FK1.1023.78AF","2.5#LCD","PCS",newdecimal(85.7));
session.Save(item1);
Itemitem2=newItem("191.1023.78AF","testitem2","PCS",newdecimal(12));
session.Save(item2);
Itemitem3=newItem("023.0000.1233","testitem3","PCS",newdecimal(25.7));
session.Save(item3);
Itemitem4=newItem("FK1.2314.ZF31","testitem4","PCS",newdecimal(1.789));
session.Save(item4);
Itemitem5=newItem("100000000070","testitem5","EA",newdecimal(19));
session.Save(item5);
Itemitem6=newItem("ANTXX00230GD","testitem6","EA",newdecimal(19));
session.Save(item6);
//创建PlantItem对象
session.Save(newPlantItem(plant1,item1,"PCS",ItemCategoryEnum.P,PurchaseCategoryEnum.JIT,StockOptionEnum.ERP));
session.Save(newPlantItem(plant1,item2,"PCS",ItemCategoryEnum.P,PurchaseCategoryEnum.PO,StockOptionEnum.None));
session.Save(newPlantItem(plant1,item3,"PCS",ItemCategoryEnum.P,PurchaseCategoryEnum.PO,StockOptionEnum.ERP));
session.Save(newPlantItem(plant1,item4,"PCS",ItemCategoryEnum.M,PurchaseCategoryEnum.JIT,StockOptionEnum.Hub));
session.Save(newPlantItem(plant1,item5,"PCS1",ItemCategoryEnum.M,PurchaseCategoryEnum.JIT,StockOptionEnum.None));
session.Save(newPlantItem(plant1,item6,"EA",ItemCategoryEnum.M,PurchaseCategoryEnum.PO,StockOptionEnum.None));
session.Save(newPlantItem(plant2,item2,"PCS",ItemCategoryEnum.M,PurchaseCategoryEnum.JIT,StockOptionEnum.ERP));
session.Save(newPlantItem(plant2,item3,"PCS",ItemCategoryEnum.M,PurchaseCategoryEnum.PO,StockOptionEnum.Hub));
session.Save(newPlantItem(plant2,item4,"PCS",ItemCategoryEnum.P,PurchaseCategoryEnum.PO,StockOptionEnum.Hub));
session.Save(newPlantItem(plant2,item5,"PCS",ItemCategoryEnum.M,PurchaseCategoryEnum.PO,StockOptionEnum.Hub));
1. Criteria 条件式查询
ICriteriacriteria=session.CreateCriteria(typeof(PlantItem));
criteria.Add(Expression.Eq("PurchaseCategory",PurchaseCategoryEnum.PO))
.AddOrder(Order.Desc("Item"))
.SetFirstResult(2).SetMaxResults(2);
IList<PlantItem>list=criteria.List<PlantItem>();
for(inti=0;i<list.Count;i++)
{
Console.WriteLine("{0}\t{1}\t\t{2}",
list[i].Plant.PlantID,list[i].Item.ItemID,list[i].PurchaseCategory.ToString());
} SQL语句:
execsp_executesqlN'
SELECTtop4this_.IDasID2_0_,this_.PLANT_IDasPLANT2_2_0_,this_.ITEM_IDasITEM3_2_0_,
this_.UNITasUNIT2_0_,this_.ITEM_CATEGORYasITEM5_2_0_,this_.PURCHASE_CATEGORYasPURCHASE6_2_0_,
this_.STOCK_OPTIONasSTOCK7_2_0_,this_.CREATE_DATEasCREATE8_2_0_,this_.CREATE_TIMEasCREATE9_2_0_
FROMTBLPLANTITEMthis_WHEREthis_.PURCHASE_CATEGORY=@p0ORDERBYthis_.ITEM_IDdesc',
N'@p0nvarchar(2)',@p0=N'PO' 条件式查询提供like、in、between等各种方式查询,基本可以满足应用的要求。
条件式查询以及HQL都是在对象层面进行query,因此提供的参数都是对象属性名称、属性值。例如上面的AddOrder语句,是要求结果集按照PlantItem对象的Item属性降序排列,NHibernate根据映射信息生成SQL,在SQL中使用的是ORDER BY ITEM_ID desc实现。
SetFirstResult、SetMaxResults可以用于实现分页,不同的数据库有不同的实现方式,例如SQL Server 2000使用的是上面取top 4的方式,然后在程序中过滤掉FirstResult之前的对象。
如果映射中配置了对象关联,可以在条件式查询中使用关联进行查询:
ICriteriacriteria=session.CreateCriteria(typeof(PlantItem))
.Add(Expression.Eq("PurchaseCategory",PurchaseCategoryEnum.PO));
ICriteriaitemCriteria=criteria.CreateCriteria("Item")
.Add(Expression.Like("ItemDescription","%testitem%"))
.AddOrder(Order.Desc("ItemDescription"));
IList<PlantItem>list=criteria.List<PlantItem>();
for(inti=0;i<list.Count;i++)
{
Console.WriteLine("{0}\t{1}\t\t{2}\t{3}",
list[i].Plant.PlantID,list[i].Item.ItemID,list[i].PurchaseCategory.ToString(),list[i].Item.ItemDescription);
} SQL语句:
execsp_executesqlN'
SELECTthis_.IDasID2_1_,this_.PLANT_IDasPLANT2_2_1_,this_.ITEM_IDasITEM3_2_1_,this_.UNITasUNIT2_1_,
this_.ITEM_CATEGORYasITEM5_2_1_,this_.PURCHASE_CATEGORYasPURCHASE6_2_1_,
this_.STOCK_OPTIONasSTOCK7_2_1_,this_.CREATE_DATEasCREATE8_2_1_,this_.CREATE_TIMEasCREATE9_2_1_,
item1_.ITEM_IDasITEM1_7_0_,item1_.ITEM_DESCRIPTIONasITEM2_7_0_,item1_.UNITasUNIT7_0_,
item1_.PRICEasPRICE7_0_
FROMTBLPLANTITEMthis_innerjoinTBLITEMitem1_onthis_.ITEM_ID=item1_.ITEM_ID
WHEREthis_.PURCHASE_CATEGORY=@p0anditem1_.ITEM_DESCRIPTIONlike@p1
ORDERBYitem1_.ITEM_DESCRIPTIONdesc',
N'@p0nvarchar(2),@p1nvarchar(11)',
@p0=N'PO',@p1=N'%testitem%' 上面的语句查询PlantItem集合对象,对PurchaseCategory下条件,也对PlantItem.Item.ItemDescription属性下条件,并要求按照PlantItem.Item.ItemDescription降序排列。NHibernate根据映射配置文件中PlantItem类与Item类之间的many-to-one关系,自动将TBLPLANTITEM与TBLITEM进行inner join查询,然后对ITEM_DESCRIPTION字段下条件,按照ITEM_DESCRIPTION排序。
另外一种条件式查询方式,是给出一个example对象,NHibernate根据传入的example对象属性是否有赋值,确定是否应该对该属性生成一个查询条件。
2. HQL
HQL语法上类似SQL,也不同于SQL。SQL面向的是数据库的database schema/data structure,HQL面向的是对象模型。
简单用法如下:
Plantplant=session.Get<Plant>("1105");
stringhql="fromNH12.MyExample.Domain.PlantItemas pi where pi.Item.ItemID=:ItemIDand pi.Plant=:Plant";
IQueryquery=session.CreateQuery(hql)
.SetString("ItemID","191.1023.78AF")
.SetEntity("Plant",plant);
IList<PlantItem>list=query.List<PlantItem>(); 首先,整个hql语句中使用的都是对象、对象属性,跟数据库table没有关系。
上面的hql要查询的是PlantItem对象列表,因为是基于对象模型,映射配置文件中PlantItem、Plant、Item对象之间的关联关系已经配置,因此在hql中可以使用这些对象以及它们之间已经建立的关联关系。既可以象ItemID一样使用Item对象的属性进行查询,也可以象Plant一样,对整个Plant属性下查询条件。SQL如下:
execsp_executesqlN'
selectplantitem0_.IDasID2_,plantitem0_.PLANT_IDasPLANT2_2_,plantitem0_.ITEM_IDasITEM3_2_,
plantitem0_.UNITasUNIT2_,plantitem0_.ITEM_CATEGORYasITEM5_2_,
plantitem0_.PURCHASE_CATEGORYasPURCHASE6_2_,plantitem0_.STOCK_OPTIONasSTOCK7_2_,
plantitem0_.CREATE_DATEasCREATE8_2_,plantitem0_.CREATE_TIMEasCREATE9_2_
fromTBLPLANTITEMplantitem0_
where(plantitem0_.ITEM_ID=@p0)and(plantitem0_.PLANT_ID=@p1)',
N'@p0nvarchar(13),@p1nvarchar(4)',
@p0=N'191.1023.78AF',@p1=N'1105' 如果是对整个对象下查询条件,NHibernate根据关联的ID生成SQL条件;如果对关联对象仅仅使用到关联用的ID字段,NHB会比较智能,不会使用join。例如上面的例子,虽然对PlantItem.Item.ItemID加了一个查询条件,但NHB并没有对这两个表使用join子句。如果上面的例子中添加一个查询条件:pi.Item.ItemDescription like :ItemDesc,这样NHB就必须要将TBLPLANTITEM和TBLITEM表join进行查询了。
hql也可以象SQL多个表join一样,对于那些在映射配置文件中并没有建立的关联关系,可以在hql中使用join。例如:
fromPlantItemaspi,Userusrwherepi.Plant.PlantID=usr.UserID
接下来我对一件事情比较感兴趣。在NHibernate考察系列 04中,PlantItem对象加了个CreateTime属性,这个属性有点特殊,在对象中使用的是一个DateTime类型的属性,而映射到数据库却是将日期和时间分别转化成字符串保存在数据库的CREATE_DATE、CREATE_TIME两个字段中。这种情况下,如果需要在hql中对这个属性下查询条件,情况会怎么样?
stringhql="fromNH12.MyExample.Domain.PlantItemaspiwherepi.CreateTime>:Time";
IQueryquery=session.CreateQuery(hql)
.SetParameter("Time",DateTime.Parse("2007-04-1416:01:01"));
IList<PlantItem>list=query.List<PlantItem>(); 测试结果,NHibernate不支持,异常信息为:path expression ends in a composite value。Hibernate能够实现这一特性将会是一件非常好的事情,但要实现这一特性不简单。以上面的例子来看,Hibernate怎样根据CreateTime>:Time这一条件生成SQL?((CREATE_DATE=@date AND CREATE_TIME>@time) OR CREATE_DATE>@date)?在不同的运用场景下情况会更复杂。
这里对上面这种情形的用户自定义类型带来相当的限制,因此慎用。
详细的hql语法参考NHibernate的文档。
个人对条件式查询、hql的看法:
1. 实现多数据库兼容。
2. 对持久化存取知识的封装。可能比较直观的想法是既然做到了多数据库兼容,数据库知识的封装作用已经不大。一方面,对象属性与持久化存储之间有差异,使用hql使得这种差异被封装在映射文件或者自定义的映射类型中。另一方面,在粗粒度对象设计的情况下,实体跟表之间基本一一对应,在对象模型层面写hql跟在table层面写SQL的确没有太多差异。但如果使用细粒度对象设计,对象模型与table之间的差异就会相当明显,domain既要处理映射行为,控制数据存取,又要处理复杂的对象关系,情况就复杂化了。分离一个DAL出来,成本代价也是比较高。
3. 性能问题。
这个问题就有点复杂化了。使用ORM之后,在应用与数据库之间建立一个隔离带,性能问题不会消失。解决性能问题有两种倾向,第一种是类似iBATIS做法,把存取数据用的SQL做分离,集中管理,方便数据库、SQL层面做优化。另一种做法是结合domain的架构设计来解决性能问题。
NHibernate的应用产生性能问题,一方面是不合理的对象关系,在数据加载方面造成浪费、开销。另一方面是不合理的条件式查询、hql的使用。确保良好的对象模型设计,根据设计思想,在条件式查询、hql的使用上制定约束、规范变得很重要。
3. Named Query
Named Query是将hql从代码中分离出来,以命名的形式放入配置文件中。这跟iBATIS的方式有点类似,便于对hql的集中管理和维护。
在PlantItem.hbm.xml配置文件的hibernate-mapping元素下添加下面的Named Query hql语句:
<queryname="NH12.MyExample.Domain.PlantItemQuery">
<![CDATA[
fromNH12.MyExample.Domain.PlantItemaspi
wherepi.Item.ItemDescriptionlike:ItemDesc
orderbypi.Plant,pi.Item
]]>
</query> 程序中使用:
IList<PlantItem>list=session.GetNamedQuery("NH12.MyExample.Domain.PlantItemQuery")
.SetString("ItemDesc","testitem5%")
.List<PlantItem>();
4. Native SQL
Native SQL提供直接对数据库访问的机会。
stringsql=@"
selectpi.PLANT_IDasPlantID,pi.ITEM_IDasItemID,i.ITEM_DESCRIPTIONasItemDescription
fromTBLPLANTITEMpi
innerjoinTBLITEMioni.ITEM_ID=pi.ITEM_ID
orderbypi.PLANT_ID,i.ITEM_DESCRIPTION
";
ISQLQueryquery=session.CreateSQLQuery(sql)
.AddScalar("PlantID",NHibernateUtil.String)
.AddScalar("ItemID",NHibernateUtil.String)
.AddScalar("ItemDescription",NHibernateUtil.String);
IListlist=query.List(); 返回的是一个object数组的列表。
用下面的方法返回实体对象列表:
stringsql=@"select*fromTBLPLANTITEM";
ISQLQueryquery=session.CreateSQLQuery(sql).AddEntity(typeof(PlantItem));
IList<PlantItem>list=query.List<PlantItem>();
用下面的方法可以一次返回多个对象列表:
stringsql=@"select{pi.*},{i.*}fromTBLPLANTITEMpi,TBLITEMiwherepi.ITEM_ID=i.ITEM_ID";
ISQLQueryquery=session.CreateSQLQuery(sql)
.AddEntity("pi",typeof(PlantItem))
.AddEntity("i",typeof(Item));
IListlist=query.List();
for(inti=0;i<list.Count;i++)
{
object[]collections=list[i]asobject[];
PlantItempi=collections[0]asPlantItem;
Itemitem=collections[1]asItem;
Console.WriteLine("{0},\t{1},\t{2},\t{3}",pi.Plant.PlantID,pi.Item.ItemID,pi.Item.ItemDescription,pi.Plant.PlantName);
Console.WriteLine("{0},\t{1}",item.ItemID,item.ItemDescription);
} 首先注意SQL语句,对需要选择的列必须使用别名,因为两个表中有一个名称相同的字段ITEM_ID。SQL语句中的{pi.*},{i.*}不是SQL语法,而是告诉NHibernate对pi.*和i.*中的所有列都使用别名,以区别选出的每一个字段是属于哪个对象的。数据库执行的SQL如下,可以看到跟我们在代码中给出的SQL已经不一样了,主要是NHB在中间进行了处理,为每个列生成了别名:
selectpi.IDasID2_0_,pi.PLANT_IDasPLANT2_2_0_,pi.ITEM_IDasITEM3_2_0_,pi.UNITasUNIT2_0_,
pi.ITEM_CATEGORYasITEM5_2_0_,pi.PURCHASE_CATEGORYasPURCHASE6_2_0_,
pi.STOCK_OPTIONasSTOCK7_2_0_,pi.CREATE_DATEasCREATE8_2_0_,pi.CREATE_TIMEasCREATE9_2_0_,
i.ITEM_IDasITEM1_7_1_,i.ITEM_DESCRIPTIONasITEM2_7_1_,i.UNITasUNIT7_1_,i.PRICEasPRICE7_1_
fromTBLPLANTITEMpi,TBLITEMi
wherepi.ITEM_ID=i.ITEM_ID 返回的IList对象是一个object[]类型,第一个元素是PlantItem对象,第二个元素是Item对象。
单步调试上面的测试代码,并监控SQL语句,可以发现另外一个小区别:访问pi.Plant.PlantName可能会产生一个SQL查询,因为相关的Plant对象可能还没有被缓存到session中;而任何时候访问pi.Item.ItemDescription,都不会再产生SQL查询,因为在执行IListlist=query.List()时相关Item对象已经一起返回,并被session缓存过了。
上面这些都是根据NHibernate的文档写的测试代码,下面的一些功能特性在文档中都讲述的比较详细,也就不再测试了。
Named SQL Query,跟NamedQuery差不多,但是需要提供另外的一些信息,例如返回的各个字段Type是什么,是否返回某一个实体对象,如果是,返回的结果集中各个字段跟实体属性的对应关系等。
可以使用存储过程,跟Named SQL Query一样,需要对返回的结果集进行额外的一些配置说明。如果存储过程返回多个DataTable(SQL Server、MySQL等),NHB只取第一个DataTable。
可以为实体的insert、update、delete使用自定义的SQL语句,可以为实体的load使用hql或者是SQL的Named Query,可以在这些里面使用存储过程。
Native SQL的支持,包括实体的insert、update、delete、load中对SQL的支持,是Hibernate留给用户解决复杂问题的最后手段了。使用Native SQL解决问题的灵活度相当大,也能使你的应用架构很好的跟NHibernate整合在一起,因此适当的使用Native SQL、NHibernate提供的扩展,在设计上会拥有相当不错的发挥空间。

