NHibernate source analysis three (continued): Data persistence

xiaoxiao2021-03-06  83

When persistent objects, it is clear that the value of the recorded value to the object property and the acquisition of the object property is used for persistence operation. For updated operations, it is necessary to check if the value of the object has changed, that is, whether Dirty, these Operation is done by the persistence class of the object. For persistence classes, refer to "Session and Persistence Action".

The source code of NH is analyzed below to understand the process of data loading and updating in NH.

First, lasting object loading

First, imagine the load process of the object (LOAD).

1. Number of records from the database according to the object ID;

2. Construct an object using the default constructor;

3. Store recorded values ​​in one place and is used to compare when saving;

4. Assign the recorded value to the properties of the object.

The first half of the object loading process has been analyzed in the previous article of this article, which is only analyzed here.

// *** EntityLoader.cs 34 line ***

Public Object Load (iSessionImplementor session, Object ID, Object obj) {

IList list = loading (session, new object [] {id}, idtype, obj, id, false;

IF (List.count == 1) {

Return List [0];

}

Else IF (list.count == 0) {

Return NULL;

}

Else {

Throw new hibernateException ("...");

}

}

When using the session.Load load object, the LOAD method of the object persistence class will be called.

// *** logader.cs 745 line ***

Protected Ilist Loadentity (ISESSITIONIMPLEMENTOR Session,

Object [] Values, ITYPE [] Types, Object OptionAlObject,

Object optionalid, Bool ReturnProxies) {

Return Dofind (Session, Values, Types, OptionalObject,

Optionalid, NULL, NULL, RETURNPROXIES, NULL, NULL, NULL;

}

Call DOFIND directly.

Private IList Dofind

ISESSIMPLEMENTOR session, Object [] VALUES,

ITYPE [] TYPES, OBJECT OPTIONALOBJECT,

Object Optionalid, PersistentCollection OptionalCollection,

Object OptionalCollectionOwner, Bool ReturnProxies,

RowSelection Selection, iDictionary namedparams,

IDictionary lockmodes) {

/ / Take the type of persistent object to be loaded, some queries only take the value of some attributes, then Persisters are empty collection.

ILoadable [] Persisters = Persisters;

INT cols = personisters.lend

Bool returnSentities = cols> 0; // Judgment whether to return to an entity (persistent object)

ArrayList HydratedObjects = RETURNSENTIIES? New ArrayList (): NULL;

Key OptionalObjectKey;

IF (optionalObject! = null) {

OptionalObjectKey = New key (optionalid, session.getPersister (optionalObject);

Else {

OptionalObjectKey = NULL;

}

IList results = new arraylist ();

IDBCommand St = NULL;

// Analyze the query string and generate idbcommand.

ST = prepareCommand

ApplyLocks (Sqlstring, Lockmodes, Session.Factory.Diact),

VALUES, TYPES, NAMEDPARAMS, Selection, False, Session;

IDataReader RS ​​= GetResultSet (ST, SELECTION, SESSION);

Try {

Key [] keys = new key [cols];

// Start processing result set.

INT country;

For (count = 0; count

For (int i = 0; i

Keys [i] = getKeyFromResultset (i, personisters [i],

(i == COLS-1)? Optionalid: NULL, RS, SESSION;

}

/ / The result is acquired and processed.

Object [] row = getrow (RS, Persisters, Suffixes, Keys, OptionalObject,

OptionAlObjectKey, LockModeArray, HydratedObjects, session;

Results.Add (GetResultColumnorrow (RS, RS, Session));

}

}

Catch (Exception E) {

Throw e;

}

Finally {

// do something.

}

// If there is an entity, the entity (ie, initializing persistent object) is initialized.

IF (returnntsentities) {

INT hydrtedObjectssize = hydratedObjects.count;

For (int i = 0; i

}

Return Results;

}

DOFIND is the end method of object loading, all data load, including HQL, Criteria queries, and finally call this method, and the part of the processing collection is omitted.

In this method, the result set is first obtained, and then processes the results (constructor and acquisition column values), and finalize the entity (setting attribute value).

// *** loader.cs 318 line ***

Private object [] getRow (iDataReader RS, iLoadable [] Persisters,

String [] Suffixes, Key [] Keys, Object OptionAlObject,

Key OptionAlObjectKey, LockMode [] LockModes, IList HydratedObjects,

ISessionImplementor session) {

INT cols = personisters.lend

Object [] RowResults = new object [color]; for (int i = 0; i

Object obj = NULL;

Key Key = Keys [i];

IF (Keys [i] == null) {

// do nothing - used to have hydrate [i] = false;

}

Else {

// if the object is already loaded, returnid one

Obj = session.getentity (key);

IF (Obj! = null) {

// ITS Already loaded so dont need to hydrate it

InstanceAlReadyloaded (RS, I, Persisters [i], suffixes [i], key, obj,

LockModes [i], session);

}

Else {

Obj = instancenotyetloaded (RS, I, Persisters [i], suffixes [i], key,

LockModes [i], optionalObjectKey, OptionAlObject, HydratedObjects, session;

}

}

RowResults [i] = OBJ;

}

Return RowResults;

}

Check the number of objects that need to return, then loop the process, you can see that the NH design is very good. When the object has been loaded, it will not be used, and HydRate is not translated (I don't very good, it is not very good). The function is to convert the value of the record set into an Object array corresponding to the object properties.

// *** loader.cs 365 line ***

Private Object Instancenotyetloaded (IdataReader DR, Int i, iLoadable Persister,

String Suffix, Key Key, LockMode Lockmode, Key OptionalObjectKey,

Object OptionAlObject, Ilist HydratedObjects, ISessionImplementor Session

{

Object obj;

// Acquire the actual type of object.

System.Type InstanceClass = GetInstanceClass (DR, I, Persister, SUFFIX,

Key.Identifier, session);

IF (optionalObjectKey! = null && key.equals (optionalObjectKey) {

Obj = OptionAlObject;

}

Else {

Obj = session.instantiate (InstanceClass, key.Identifier);

}

// NEED TO HYDRATE IT

LockMode AcquidlockMode = LOCKMODE == LockMode.none? LockMode.Read: LockMode;

LoadFromResultSet (DR, I, Obj, Key, SUFFIX, ACQUIREDLOCKMODE, PERSISTER, SESSION);

// Materialize associations (and initialize the object) later hydratedObjects.add (obj);

Return Obj;} There are two very important code, which confirms our previous conjecture, one is the session.instantiate method, this is used to construct the object; the other is loadFromResultSet, which converts the result set to the object set object Object array . // *** Session.cs 1769 rows *** public object Instantiate (System.Type clazz, object id) {return Instantiate (factory.GetPersister (clazz), id);} public object Instantiate (IClassPersister persister, object id) {Object Result = Interceptor.inSTANTIAT (PERSISTER.MAPPEDCLASS, ID); if (result == null) Result = persister.instantiate (id); Return Result;} Before instantiation, NH handed over the control to Interceptor (interception Apparatus, we can implement an instance object in the interceptor and return, if not NULL is returned, NH will no longer perform instantiation; if it is empty, the object is constructed by the object. // *** AbstractEntityPersister 227 rows *** public virtual object Instantiate (object id) {if (hasEmbeddedIdentifier && id.GetType () == mappedClass) {return id;} else {if (abstractClass) throw new HibernateException ( ". .. "); Try {return constructor.invoke (null);} catch (exception e) {throw new instantiationException (" ... ");}}} constructor is a constructorinfo object, a friend who reflects familiarity should know this Is a constructor object, this constructor assigns a value in the constructor of the AbstractityPersister, which is a constructor that does not have any parameters.

// *** Loader.cs 427 rows *** private void LoadFromResultSet (IDataReader rs, int i, object obj, Key key, string suffix, LockMode lockMode, ILoadable rootPersister, ISessionImplementor session) {session.AddUninitializedEntity (key, obj, lockMode); // Get the persister for the subclass ILoadable persister = (ILoadable) session.GetPersister (obj); string [] [] cols = persister == rootPersister suffixedPropertyColumns [i]:? GetPropertyAliases (suffix, persister); object id = Key.Identifier; Object [] Values ​​= Hydrate (RS, ID, Obj, Persister, session, cols); session.posthydrate (PERSISTER, ID, VALUES, OBJ, LOCKMODE);} The uninitialized object will be added to Session In the entity collection, then the value corresponding to the attribute is then acquired from the row, and finally passes the value to the session. // *** Loader.cs 492 rows *** private object [] Hydrate (IDataReader rs, object id, object obj, ILoadable persister, ISessionImplementor session, string [] [] suffixedPropertyColumns) {IType [] types = persister.PropertyTypes Object [] value = new object [types.lend "; for (int i = 0; i

Regarding the collection of entities, refer to the previous "session and persistence operation" one in this article, now the object is also constructed, and the value corresponding to the attribute is also obtained, the last step is, the value assigns the value to the object, // * ** Session.cs 2257 rows *** public void InitializeEntity (object obj) {EntityEntry e = GetEntry (obj); IClassPersister persister = e.Persister; object [] hydratedState = e.LoadedState;; object id = e.Id IType [] Types = persister.propertyTypes; Interceptor.onLoad (Obj, ID, HydratedState, Persister.propertyNames, Types); for (int i = 0; i

Second, lasting object update and dirty status. Update steps: 1. Acquire the object value of the object; 2. Get the initial status value in the session entity collection; 3. Compare the value of 1 and 2, see if there is a change, such as changing , Set Dirty to true; 4. Decide whether to update the object according to the status of Dirty; 5. If there is an update object, change the initial status value as a new attribute value. The first half of the object update process has been analyzed in the previous section of this article, which is only analyzed here. In the previous article, we have pointed out that when the UPDATE object, the object does not update immediately, but put a collection, only the true update operation is performed only if the FLUSH method is called. Some operations will automatically call the Flush method, such as submitting transactions. // *** session.cs 2328 line *** public void flush () {flusheveryhes (); execute (); postflush ();} // *** session.cs 2341 line *** private void flusheverything {Interceptor.Preflush; preflushenTITIES (); preflushcollections (); flushenTITIES (); flushcollections ();} Call the interceptor's preflush method to pass all entities in the session.

// *** Session.cs 2469 rows *** private void FlushEntities () {ICollection iterSafeCollection = IdentityMap.ConcurrentEntries (entries); foreach (DictionaryEntry me in iterSafeCollection) {EntityEntry entry = (EntityEntry) me.Value; Status status = Entry.status; if (status! = status.loading && status! = status.gone) {Object obj = me.key; iClassPersister Persister = entry.Persister; Object [] value; if (status == status.deleted) { values ​​= entry.DeletedState;} else {values ​​= persister.GetPropertyValues ​​(obj);} IType [] types = persister.PropertyTypes; bool substitute = Wrap (values, types); bool cannotDirtyCheck; bool interceptorHandledDirtyCheck; int [] dirtyProperties = interceptor .Finddirty (Obj, entry.id, values, entry.loadedstate, personister.propertynames, types); if (dirtyproperties == null) {InterceptorHandLiRTyCheck = False; cannotDirtyCheck = entry.LoadedState == null; if (cannotDirtyCheck!) {DirtyProperties = persister.FindDirty (values, entry.LoadedState, obj, this);}} else {cannotDirtyCheck = false; interceptorHandledDirtyCheck = true;}

if (persister.IsMutable && (cannotDirtyCheck || (dirtyProperties! = null && dirtyProperties.Length! = 0) || (status == Status.Loaded && persister.IsVersioned && persister.HasCollections && SearchForDirtyCollections (values, types)))) {// The object is dirty (DIRTY). bool intercepted = interceptor.OnFlushDirty (obj, entry.Id, values, entry.LoadedState, persister.PropertyNames, types); if (!! intercepted && cannotDirtyCheck && interceptorHandledDirtyCheck) {dirtyProperties = persister.FindDirty (values, entry.LoadedState, obj , this);} substitute = substitute || intercepted; if (status == Status.Loaded && persister.ImplementsValidatable) {((IValidatable) obj) .Validate ();} object [] updatedState = null; if (status == Status.Loaded) {updatedState = new object [values.Length]; TypeFactory.DeepCopy (values, types, persister.PropertyUpdateability, updatedState);} updates.Add (new ScheduledUpdate (entry.Id, values, dirtyProperties, entry.Version, NextVersion, Obj, UpdatedState, Persister, this));}}}} First get the value of the property, this passing object The getPropertyValues ​​method for persistence classes is available, which is the opposite of setPropertyValues, which sets the object attribute in front; then finds the changed properties, NH handed over the control to the Interceptor (interceptor), in Finddirty, we can set it Attributes that have changed, this looks a good process, for example: in the field level permissions, can limit some attributes to be changed according to the permissions.

If not processed, you must remember to return a NULL value. At this time, you will find the changed properties (FindiRTY method), if you check the object is dirty (Dirty), follow-up processing. The next NH once again handed the control over the interceptor. This call is ONFLUSHDIRTY, so that we have got a chance to handle the object, as for what you can do, then you have to see everyone's imagination, the simplest is recorded Update the log log. :-) Note that this method has a return value, the default is to return false, if you modify the content of Values, and need to update, then True should be returned, which will call the object's persistent class, the premise is that you are not Handling the FindiRTY method for Interceptor. Next, check if the object is implemented, if an implementation calls the ivalidatable.validate method. It is highly recommended that all persistent objects should implement IValidable, although some constraints can be specified in the mapping file, but these constraints obviously be better than in the IVALIDATABLE interface, a relatively secure data check interface can be provided. (If the verification fails, throw a validateException, and attach an error message that the verification failed), which is like a simple processing as long as a simple process is represented. Finally, the object is added to the update collection and wait for the next step. // *** Session.cs 2392 rows *** private void Execute () {try {ExecuteAll (insertions); insertions.Clear (); ExecuteAll (updates); updates.Clear (); ExecuteAll (collectionRemovals); collectionRemovals. Clear (); ExecuteAll (collectionUpdates); collectionUpdates.Clear (); ExecuteAll (collectionCreations); collectionCreations.Clear (); ExecuteAll (deletions); deletions.Clear ();} catch (Exception e) {throw new ADOException ( "could NOT SYNCHRONIZE DATABASE State with session ", e);} Processing all objects stored in task collections. // *** session.cs 2450 line *** Private void ExecuteAll (Icollection Coll) {Foreach (IExecutable E IN COLL) {EXECUTIONS.ADD (E); E.execute ();} if (Batcher! = Null) Batcher.executebatch ();} Traverse Coll, then call the IExecutable.execute method for real persistence operations.

// *** scheduledupdate.cs 33 line *** public override void execute () {if (person.hascache) Persister.cache.lock (ID); persister.Update (ID, Fields, Dirtyfields, Lastversion, Instance, Session ); Session.postupdate (instance, updatedstate, next ";} Update the operation by the persistence class of the object. // *** EntityPersister.cs 998 rows *** protected virtual void Update (object id, object [] fields, bool [] includeProperty, object oldVersion, object obj, SqlString sqlUpdateString, ISessionImplementor session) {if (! HasUpdateableColumns) return ; IDbCommand statement = null; statement = session.Batcher.PrepareBatchCommand (sqlUpdateString); try {int versionParamIndex = dehydrate (id, fields, includeProperty, statement, session); session.Batcher.AddToBatch (1);} catch (Exception e) {Session.batcher.abortbatch (e); throw e;}} The above code looks a bit strange, but it is obviously the code that has not been completed. Dehydrate is used to assign a value value corresponding to the field to the parameter of Statement. // *** Session 2438 rows *** public void PostUpdate (object obj, object [] updatedState, object nextVersion) {EntityEntry e = GetEntry (obj); e.LoadedState = updatedState; e.LockMode = LockMode.Write; if (E.PERSISTER.ISVERSIONED) {E.Version = NextVersion; E.Persister.SetPropertyValue (Obj, E.PERSISTER.VERSITIONPROPERTY, NEXTVERSION);}} Tuning the status of the object in the session entity collection. // *** Session.cs 2852 line *** private void PostFlush () {foreach (DictionaryEntry de in IdentityMap.ConcurrentEntries (collections)) {((CollectionEntry) de.Value) .PostFlush ((PersistentCollection) de.Key) } Interceptor.postflush (entitiesbykey.values);} Call the POSTFLUSH method of the interceptor to pass all entities in the session.

转载请注明原文地址:https://www.9cbs.com/read-104886.html

New Post(0)