對象標識相當於資料表中的主鍵,在持久化中起著十分重要的作用,nhibernate通過對象標識來辨別兩個持久對象是否相等。
在對應檔中,通過id屬性來定義對象標識,內容如下:
其中unsaved-value屬性用來指明對象未持久化時的值,如果此值與未持久化的對象標識值不符,將無法save對象,generator用於指定標識對象的類型,常用的有identity, assigned等。
標識對象為實現IIdentitierGenerator介面的類,由IdentitierGeneratorFactory類根據對應檔的標識類型來建立,IIdentifierGenerator定義了Generate方法,用於產生對象標識。
1. 標識對象的建立
標識對象在持久化類AbstractEntityPersister中建立,通過它我們就可以對持久對象的標識進行操作了。
//*** AbstractEntityPersister.cs ***
public virtual IIdentifierGenerator IdentifierGenerator {
get {
if (idgen==null) {
throw new HibernateException("...");
}
return idgen;
}
}
idgen在建構函式中被賦值。
protected AbstractEntityPersister(PersistentClass model, ISessionFactoryImplementor factory) {
// ...
// GENERATOR
idgen = model.Identifier.CreateIdentifierGenerator(dialect);
useIdentityColumn = idgen is IdentityGenerator;
identitySelectString = useIdentityColumn ? dialect.IdentitySelectString : null;
// ...
}
其中model為PersistentClass或其子類,Identifier為Value類型的屬性。
// *** Value.cs ***
public IIdentifierGenerator CreateIdentifierGenerator(Dialect.Dialect dialect) {
if ( uniqueIdentifierGenerator==null ) {
uniqueIdentifierGenerator = IdentifierGeneratorFactory.Create(identifierGeneratorStrategy, type, identifierGeneratorProperties, dialect);
}
return uniqueIdentifierGenerator;
}
//*** IdentitifierGeneratorFactory ***
public static IIdentifierGenerator Create(string strategy, IType type, IDictionary parms, Dialect.Dialect dialect) {
try {
System.Type clazz = (System.Type) idgenerators[strategy];
// ...
if (clazz==null) clazz = System.Type.GetType(strategy);
IIdentifierGenerator idgen = (IIdentifierGenerator) Activator.CreateInstance(clazz);
if (idgen is IConfigurable) ((IConfigurable) idgen).Configure(type, parms, dialect);
return idgen;
}
catch (Exception e) {
throw new MappingException("could not instantiate id generator", e);
}
}
Create方法通過標識對象類名來建立標識對象。
2. 標識對象在持久化中的使用
在會話和持久化操作一文,我曾提到當前會話會把要持久化的Object Storage Service起來,直到調用Flush或關閉會話。儲存持久對象的集合為entitiesByKey,這是一個Hashtable,它的key為一個Key對象, value為持久對象,Key對象簡單的儲存持久對象的id和IdentifierSpace。
在進行持久化操作時,nhibernate必須首先檢查對象是否在entitiesByKey中,這由GetEntity方法完成,然後再根據對象是否在集合中作後續處理。
//*** SessionImpl.cs ***
public object GetEntity(Key key) {
return entitiesByKey[key];
}
下面來看看DoUpdate中的處理:
private void DoUpdate(object obj, object id) {
// ...
Key key = new Key(id, persister);
object old = GetEntity(key);
if (old==obj) {
throw new AssertionFailure("Hibernate has a bug in Update() ... or you are using an illegal id type");
}
else if ( old!=null ) {
throw new HibernateException("Another object was associated with this id ( the object with the given id was already loaded)");
);
// ...
AddEntity(key, obj);
AddEntry(obj, Status.Loaded, null, id, persister.GetVersion(obj), LockMode.None, true, persister);
// ...
}
如果首次對持久對象執行Update,此時old為空白,操作順利執行,並且對象被加入到集合中,
當再次調用Update時(在同一會話中,並且沒有調用會導致Flush的操作),此時old不為空白,將引發一個異常。