NHibernate 3.X 之 第四章 持久化类
第4章. 持久化类
持久性类是在应用程序中实现的业务问题(例如客户和电子商务应用程序中的顺序)实体的类。持久化类有,顾名思义,瞬变和也持续实例存储在数据库中。
NHibernate工程最佳如果这些类采用一些简单的规则,也称为普通老式CLR对象(POCO)编程模型。
4.1.一个简单的 POCO示例大多数.net应用程序需要一个持久类,它表示猫儿。
using System;
using Iesi.Collections;
namespace Eg
{
public class Cat
{
long id; // identifier
public virtual long Id
{
get { return id; }
protected set { id = value; }
}
public virtual string Name { get; set; }
public virtual Cat Mate { get; set; }
public virtual DateTime Birthdate { get; set; }
public virtual float Weight { get; set; }
public virtual Color Color { get; set; }
public virtual ISet Kittens { get; set; }
public virtual char Sex { get; set; }
// AddKitten not needed by NHibernate
public virtual void AddKitten(Cat kitten)
{
kittens.Add(kitten);
}
}
}
有四个主要遵循的规则在这里:
4.1.1.为持久化字段声明的属性
Cat声明所有持久性字段的属性。许多其他 ORM工具直接坚持的实例变量。我们相信它是远好于解耦从持久性机制执行细节。NHibernate仍然存在的属性,使用他们的 getter 和 setter方法。
Properties need not be declared public - NHibernate can persist a property with aninternal, protected, protected internal or private visibility.
属性需要不能声明为public - NHibernate支持对internal、protected、protected internal或private属性的持久化。
在示例中所示,支持自动属性和具有支持字段的属性。
4.1.2.实现一个默认构造函数
Cat有一个隐含的默认(无参数)构造。NHibernate的所有的持久化类必须有一个默认的构造(可能非公有制),所以可以使用Activator.CreateInstance()实例化它们。
4.1.3.提供一个标识符属性(可选)
Cat有一个Id属性,这个属性保存数据库表的主键的列。该属性可以说是任何事物,它的类型可能是基类型(primitive type)、字符型(String)、日期型(System.DateTime)。(如果你的老式数据库表有组合键,你甚至可以使用这些类型的属性的一个用户定义的类 - 看到下面复合标识符部分。)
标识符属性是可选的。你可以把它关闭,让NHibernate的内部保持追踪对象的识别。然而,对于许多应用程序,它仍然是一个很好的(非常受欢迎)的设计决策。
更重要的是,一些功能仅适用类的声明标识符属性可用:
级联更新:Cascadedupdates (请参阅"生命周期对象")
ISession.SaveOrUpdate()
我们建议你对持久化类声明命名一致的标识属性
4.1.4.介绍非密封(non-sealed)类和虚(virtual)方法(可选)
NHibernate一个中心功能:代理,取决于持久性类的非密封(non-sealed)和其所有公共方法(public methods)、属性(properties)和事件(events)声明为虚拟(virtual)的。另一种可能性是,这类以实现一个接口(interface),它声明了所有公共成员(public members)。
你可以坚持不实现接口,并没有使用NHibernate的虚拟成员(virtual members)的密封(sealed)类,但您将无法使用代理(proxies)-这将限制性能调优(performancetuning)选项。
4.2.实现继承(inheritance)子类(subclass)也必须遵守第一和第二的规则。它从Cat继承了的标识属性。
using System;
namespace Eg
{
public class DomesticCat : Cat
{
public virtual string Name { get; set; }
}
}
4.3.实现 Equals()和GetHashCode()方法
你要重写的 Equals()和GetHashCode()的方法,如果你打算混合持久性类(如在一个ISet中)的对象。
这仅适用于如果这些对象被加载,在两个不同ISessions,作为NHibernate的唯一保证标识(a==b,Equals()方法的默认实现)在一个单一的ISession!
即使这两个对象A和B是相同的数据库行(他们有相同的主键值作为其标识符),我们不能保证,他们是一个特定的ISession上下文以外的同一个对象实例。
最明显的方法是通过比较两个对象的标识符值执行 Equals()/GetHashCode()。如果值是相同的都必须是同一数据库的行,因此它们相等(如果两者都添加到ISet,我们将只具有ISet中一个元素)。不幸的是,我们不能使用这种方法。NHibernate将只是持久的对象指派标识符值,新创建的实例不会有任何标识符值!我们建议实施 Equals()和 GetHashCode() 使用业务键平等(Business key equality)。
业务键平等指的是 Equals()方法仅仅比较形成业务键,它将确定我们在现实世界中(一种天然的候选键(candidatekey))的实例的属性:
public classCat
{
...
public override bool Equals(objectother)
{
if(this == other)return true;
Cat cat = otherasCat;
if (cat ==null)return false;// null or not a cat
if(Name != cat.Name)return false;
if(!Birthday.Equals(cat.Birthday))return false;
return true;
}
public override intGetHashCode()
{
unchecked
{
intresult;
result = Name.GetHashCode();
result = 29 * result + Birthday.GetHashCode();
return result;
}
}
}
请记住,我们的候选键(在本例中是复合的姓名和生日)的,这只是对特定比较操作(也许甚至只在一个单一的使用案例)有效。我们不需要我们通常适用于真正的主键(primary key)的稳定条件!
4.4.动态(Dynamic)模型注意以下功能目前还在试验,在不久的将来可能会更改。
持久性实体不一定要将表示为在运行时的 POCO类。NHibernate还支持动态模型(在运行时使用Dictionarys字典)。使用此方法,您不要写持久化类、只映射文件。
默认情况下,NHibernate在正常POCO模式下工作。您可以将默认实体表示模式设置为特定的ISessionFactory使用default_entity_mode配置选项(请参阅表 3.2,“NHibernate 配置属性”.
下面的例子演示了使用映射—ictionary)。首先,在映射文件,一个entity-name已被宣布为代替(或补充)一类的名称:
<hibernate-mapping>
<classentity-name="Customer">
<idname="id"
type="long"
column="ID">
<generatorclass="sequence"/>
</id>
<propertyname="name"
column="NAME"
type="string"/>
<propertyname="address"
column="ADDRESS"
type="string"/>
<many-to-onename="organization"
column="ORGANIZATION_ID"
class="Organization"/>
<bagname="orders"
inverse="true"
lazy="false"
cascade="all">
<keycolumn="CUSTOMER_ID"/>
<one-to-manyclass="Order"/>
</bag>
</class>
</hibernate-mapping>
请注意即使关联声明使用目标类名,关联的目标类型可能也是一个动态的实体,而不是 POCO。
为ISessionFactory dynamic-map设置默认的实体模式后,我们可以运行在Dictionaries的Dictionaries:
using(ISession s =OpenSession())
using(ITransactiontx = s.BeginTransaction())
{
// Create a customer
var frank = new Dictionary<string,object>();
frank["name"] = "Frank";
// Create an organization
var foobar = new Dictionary<string,object>();
foobar["name"] = "Foobar Inc.";
// Link both
frank["organization"] = foobar;
// Save both
s.Save("Customer", frank);
s.Save("Organization", foobar);
tx.Commit();
}
动态映射的优点是不需要实体类实现原型的快速周转时间。然而,你失去了编译时类型检查,将很有可能处理在运行时的许多例外。由于NHibernate的映射,数据库架构可以方便地归一化和声音,允许稍后在顶部添加正确的域模型实现。
实体表示模式也可以设置每一个ISession基础上:
using (ISession dynamicSession = pocoSession.GetSession(EntityMode.Map))
{
// Create a customer
var frank = new Dictionary<string,object>();
frank["name"] = "Frank";
dynamicSession.Save("Customer", frank);
...
}
// Continue on pocoSession
请注意,使用EntityMode调用GetSession()是对ISession API,而不是ISessionFactory。这样,新的ISession共享底层ADO连接、事务、和其他的上下文信息。这就是说你不必调二次对的ISession的Flush()和Close(),并把事务和连接处理工作的主要单元。
4.5. Tuplizers
NHibernate.Tuple.Tuplizer及其子接口(sub-interfaces)是负责管理特定的一块数据表示,鉴于代表的NHibernate.EntityMode.如果某一给定的数据被认为是一种数据结构,那么Tuplizer就是知道如何创建这样一个数据结构以及如何从中提取值和值注入这种数据结构的事情。例如,对于 POCO 实体模式中,correpsondingtuplizer知道如何创建通过其构造函数POCO以及如何访问使用POCO属性定义的属性访问。有两种高级类型的定义的tuplizer,由NHibernate.Tuple.Entity.IEntityTuplizer和NHibernate.Tuple.Component.IComponentTuplizer接口所表示。IEntityTuplizers负责管理上述合同在实体方面,而IComponentTuplizers做组件一样。
用户也可以定义他们自己的tuplizers插件。或许您需要System.Collections.IDictionary实现除了在dynamic-map entity-mode下使用System.Collections.Hashtable;或者你需要在默认情况下使用的定义不同的代理生成策略。两者都将实现通过定义自定义tuplizer执行。Tuplizers定义附加到该实体或他们为了管理组件映射。回到我们的客户实体的示例:
<hibernate-mapping>
<classentity-name="Customer">
<!--
Override the dynamic-map entity-mode
tuplizer for the customer entity
-->
<tuplizerentity-mode="dynamic-map"
class="CustomMapTuplizerImpl"/>
<idname="id"type="long"column="ID">
<generatorclass="sequence"/>
</id>
<!-- other properties-->
...
</class>
</hibernate-mapping>
public classCustomMapTuplizerImpl : NHibernate.Tuple.Entity.DynamicMapEntityTuplizer
{
// override the BuildInstantiator() method to plug in our custom map...
protected override IInstantiator BuildInstantiator(NHibernate.Mapping.PersistentClass mappingInfo)
{
return new CustomMapInstantiator(mappingInfo);
}
private sealed class CustomMapInstantiator : NHibernate.Tuple.DynamicMapInstantiator
{
// override the generateMap() method to return our custom map...
protected override IDictionary GenerateMap()
{
return new CustomMap();
}
}
}
4.6.生命周期(Lifecycle)回调
任意的持久化类可能会实现的接口ILifecycle,提供一些允许持久性对象以执行必要的初始化清理的回调后保存或加载之前删除或更新。
NHibernate的IInterceptor提供了干扰更少的替代方案,犹如:
public interface ILifecycle
{
LifecycleVeto OnSave(ISession s); (1)
LifecycleVeto OnUpdate(ISession s); (2)
LifecycleVeto OnDelete(ISession s); (3)
void OnLoad(ISession s,object id); (4)
}
(1)
OnSave - called just before the object is saved or inserted
(2)
OnUpdate - called just before an object is updated (when the object is passed to ISession.Update())
(3)
OnDelete - called just before an object is deleted
(4)
OnLoad - called just after an object is loaded
OnSave(), OnDelete()和OnUpdate()可以用来级联保存和删除依赖的对象。这是在映射文件中声明级联操作的替代.OnLoad()可用于初始化对象从其持久性状态瞬变特性.它不可能用于加载依赖对象,因为 ISession 接口可能不会从内部调用此方法. OnLoad()、 OnSave()和 OnUpdate()的进一步预期的用法是存储供以后使用的当前 ISession的引用.请注意,OnUpdate()是不要求每次更新的对象持久性状态时。瞬态的对象传递到 ISession.Update()时才调用它.
如果 OnSave()、 OnUpdate()或 OnDelete()返回 LifecycleVeto.Veto,该操作是默默地否决了。如果抛出 CallbackException时,该操作被否决,异常传递回应用程序。
请注意,OnSave()将标识符时分配给该对象,除非使用本机的密钥生成之后被调用。
4.7. IValidatable回调如果持久化类需要检查前的状态是坚持不变的,它可以实现以下接口:
public interfaceIValidatable
{
voidValidate();
}
如果不变违反了该对象应该抛出ValidationFailure。Validatable的实例不应从Validate()里面更改其状态。
不同的 ILifecycle接口的回调方法,可能在不可预知的时间称为Validate()。应用程序不应依赖业务功能对Validate()调用。