[Study Note] NHibernate In Action 20100524

Part 2 NHibernate deep dive

4 Working with persistent objects

lifecycle of objects - how an object becomes persistent, and how it stops being considered persistent - and the method calls and other actions that trigger these transitions.

how to retrieve a graph of objects with a minimal number of database hits.

4.1 The persistence lifecycle

persistence lifecycle - the state and lifecycle of an object with respect to persistence.

States of an object and transitions in an NHibernate application

Transient
Persistent
Detached

In persistence lifecycle, an object can transition from a transient object to a persistent object to a detached object.

4.1.1 Transient objects

transient - the object, which simply created using the new operator, aren't associated with any database table row.

NHibernate considers all transient instances to be nontransactional; a modification to the state of a transient instance isn't made in the context of any transaction.

NHibernate doesn't provider roll back any object changes.

Objects that are referenced only by other transient instances are, by default, also transient.

4.1.2 Persistent objects

A persistent instance is any instance with a database identity.

persistent instance are always associated with an ISession and are transactional.

automatic dirty checking

dirty - an object with modifications that haven't yet been propagated to the database.

transparent transaction-level write-behind - NHibernate propagates state changes to the database as late as possible but hides this detail from the application.

by default, NHibernate includes all columns in the SQL UPDATE statements.

dynamic-update="true"

4.1.3 Detached objects

process-scoped identtity

Detached instances may no longer be guaranteed to be synchronized with database state; they're no longer under the management of NHibernate.

The ability to return objects from one transaction to the presentation layer and later reuse them in a new transaction is one of NHibernate's main seling points.

application transactiosn

Evict() typically used only for cache management ( a performance consideration ).

4.1.4 The scope of object identity

scope of object identity - the conditions under which .NET identity is equivalent to database identity.

  • A primitive persistence layer with no identity scope makes no guarantees that if a row is accessed twice, the same .NET object instance will be returned to the application.
  • A persistence layer using transaction-scoped identtiy guarantees that, in the context of a single transaction, only one object instance represents a particular database row.
  • Process-scoped identity goes one step further and guarantees that there is only one object instance representing the row in the whole process (.NET CLR).

NHibernate implements transaction-scoped identity.

the NHibernate identity scope is the ISession instance, so identical objects are guaranteed if the same persistence manager (the ISession) is used for several operations.

If you request two objects using the same database identifier value in the same ISession, the result will be two references to the same in-memory object.

4.1.5 Outside the identity scope

reference to a detached object - an object reference leaves the scope of guaranteed identity

NHibernate supports selective reassociation of detached instances.

When override Equals(), must always also override GetHashCode(). the two method are consistent.

If two objects are equal, they must have the same hash code.

Using Database Identifier Equality

public override bool Equals(object other)
{
    if (object.ReferenceEquals(this, other))
    {
        return true;
    }
    if (this.Id == null)
    {
        return false;
    }
    if (!(other is User))
    {
        return false;
    }
    User that = (User)other;
    return this.Id == that.Id;
}

public override int GetHashCode()
{
    return Id == null? base.GetHashCode(this) : Id.GetHashCode()
}

problem: NHibernate doesn't assign identifier values until an entity is saved.

... can fix this problem by assigning an identifier yourself at the creation of the entity and using versioning to distinguish transient and detached instances.

Comparing By Value

include all persistent properties of the persistent class, apart from any database identifier propery, in the Equals() comparison.

"all properties" don't mean to include collections. Collection state is associated with a different table.

two problems:

  1. Instances from different sessions are no longer equal if one is modified.
  2. Instances with different database identity (instance that represent different rows of the database table)  can be considered equal, unless some combination of properties is guaranteed to be unique (the database columns have a unique consttraint).

Using Business Key Equality

A business key is a property, or some combination of properties, that is unique for each instance with the same database identity.

Business key equality means that the Equals() method compares only the properties that form the business key.

4.1.6 Implementing Equals() and GetHashCode()

4.2 The persistence manager

persistence manager API

  • Performing basic CRUD oprations
  • Executing queries
  • Controlling transactions
  • Managing the transaction-level cache

The persistence manager can be exposed by several different interfaces( in the case of NHibernate, they include ISession, IQuery, ICriteria, and ITransaction)

At the beginning of a unit of work, you create an instance of ISession using the application's ISessionFactory.

The central interface between the application and NHibenrate is ISession; it's your starting point for all operations just listed.

The application may have multiple ISessionFactorys if it access multiple datasources.

createion of an ISessionFactory is extremely expensive.

ISession creation is extremely inexpensive; the ISession doesn't even obtain an ADO.NET IDbConnection until a connection is required.

4.2.1 Making an object persistent

the NHibernate ISession never executes any SQL statement until absolutely neccessary.

all database operations in a transaction scope are atomic - they completely successd or completedly fail.

NHibernate does not roll back in-memeory changes to persistent objects; their state remains exactly as you left it.

4.2.2 Updating the persistent state of a detached instance

call to Update() is used to reassociate the detached instance with the new ISession and the current transaction.

select-before-update

A call to Lock() associates the object with the ISession without forcing NHibernate to treat the object as dirty.

Changes made before the call to Lock() aren't propagated to the database.

LockMode.None : NHibernate not to perform a version check or obtain any database-level locks when reassociating the object with the ISession.
LockMode.Read | LockMode.Upgrade : NHibernate would execute a SELECT statement in order to perform a version check (and to set an upgrade lock)

4.2.3 Retrieving a persistent object

Get() : retrieval by identifier

If no row with the given identifier value exists in the database, the Get() returns null.

4.2.4 Updating a persistent object transparently

automatic dirty checking - NHibernate will track and save the changes you make to an object inside a session

<property name="hibernate.adonet.batch_size">16</property>

4.2.5 Making an object transient

4.3 Using transitive persistence in NHibernate

Transitive persitence is a technique that allows you to propagate persistence to transient and detached subgraphs automatically.

4.3.1 Persistency by reachability

persistence by reachability - if any instance becomes persistent when the application creates an object reference to the instance from another instance that is already persistence. This behavior is illustrated by the object diagram.

Persistence by reachability is a recursive algorithm: all objects reachable from a persistent instance become persistent either when the original instance is made persistent or just before in-memory state is synchronized with the data store.

4.3.2 Cascading persistence with NHibernate

cascade style

  • cascade="none", the default, tells NHibernate to navigate to ignore the association.
  • cascade="save-update", NHibernate to navigate the association when the transaction is commited and when an object is passed to Save() or Update() and save newly instantiated transient instances and persist changes to detached instances.
  • cascade="delete", NHibernate to navigate the association and delete persistent instances when an object is passed to Delete()
  • cascade="all", cascade both save-update and delete, as well as calls Evict and Lock.
  • cascade="all-delete-orphan", the same as cascade="all", but in addition NHibernate deletes any persistent entity instance that has been removed (dereferenced) from the association (for example, from a collection)
  • cascade="delete-orphan", NHibernate delete any persistent entity instance that has been removed (dereferenced) from the association (for example, from a collection).
4.3.3 Managing auction categories
4.3.4 Distinguishing between transient and detached instances

NHibernate assumes that an instance is an unsaved transient instance if

  • The identifier property (if it exist) is null
  • The version perperty (if it exist) is null
  • You supply an unsaved-value in the mapping document for the class, and the value of the identifier property matches.
  • You supply an unsaved-value in the mapping document for the version property, and the value of the version prperty matches.
  • You supply an NHibernate IInterceptor and return true from IInterceptor.IsUnSaved() after checking the instance in your code.

4.4 Retrieving objects

  1. Navigating the object graph, starting from an already loaded object, by accessing the associated objects through propery accessor methods such as aUser.Address.City. NHibernate automatically loads (or preloads) nodes of the graph while you navigate the graph if the ISession is open.
  2. Retrieving by identifier, which is the most convenient and performant method when the unique identifier value of an object is known.
  3. Using Hibernate Query Language (HQL), which is a full object-oriented query language
  4. Using the Nhibernate ICriteria API, which provides a type-safe and object-oriented way to perform queries without the need for string manipulation. This facility includes queries based on an example object.
  5. Using native SQL queries and having NHibernate take care of mapping the ADO.ENT result sets to graphs of persistent objects.
  6. Using LINQ for NHibernate.

fetching strategy - that is, a strategy that defines what part of the persistent object graph should be retrieved.

4.4.1 Retrieving objects by identifier

Load() - If Load() can't find the object in the cache or database, an exception is thrown. The Load() method never returns null.

Get() - The Get() method returns null if the object can't be found.

The Load() method may return a proxy instead of a real persistent instance (when lazy loading is enable).
The Get() method never return a proxy because it must return null if the entity doesn't exist.

A proxy is a placeholder tha triggers the loading of the real object when it's accessed for the first time.

If you're cetain the persistent object exists, and nonexistence would be considered exceptional, Load() is a good option.

If you aren't certain there is a persistent instance with the given identifier, use Get() and test the return value to see if it's null.

4.4.2 Introducing Hibernate Query Language

Hibernate Query Language (HQL) is an object-oriented dialect of the familiar relational query language SQL.

HQL is used only for object retrieval not for updating, inserting, or deleting data.

HQL supports:

  1. Applying restrictions to properties of associated objects related by reference or held in collections (to navigate the object graph using query language).
  2. Retrieving only properties of an entity or entties, without the overhead of loading the entity itself in a transactional scope. report query | projection
  3. Ordering the query's result.
  4. Paginating the result.
  5. Aggregating with group by, having, and aggregate functions like sum, min, and max
  6. Performing outer joins when retrieving multiple objects per row.
  7. Calling user-defined SQL functions.
  8. Performing subqueries (nested queries).
4.4.3 Query by Criteria

QBC - Query by Criteria

An ICriteria is a tree of ICriterion instance. The Expression class provides static factory methods that return ICriterion instance.

4.4.4 Query by Example

QBE - Query by Example

The idea behind QBE is that the application supplies an instance of the queried class with certain property values set (to nondefault values).

4.4.5 Fetching strategies

NHibernate lets you specify a default fetching strategy in the mapping file and then override it at runtime in code.

fetching strategies:

  1. Immediate fetching - The associated object is fetched immediately, using a sequential database read (or cache lookup).
  2. Lazy fetching - The associated object or collection is fetched "lazily", when it's first accessed.
  3. Eager fetching - The associated object or collection is fetched together with the owning object, using a SQL outer join, and no further database request is required.
  4. Batch fetching - This approach may be used to improve the performance of lazy fetching by retrieving a batch of objects or collection when a lazy association is accessed.

Eager fetching is more common to specify the use of this strategy at runtime for a particular HQL or criteria query.

eager fetch is almost always faster, batch fetching is useful for inexperienced user who wish to achieve acceptable performance in NHibernate without having to think too hard about the SQL that will be executed.

4.4.6 Selecting a fetching strategy in mappings

* Single Point Associations

For a <many-to-one> or <one-to-one> association, lazy fetching is possible only if the associated class mapping enable proxying.

When retrieve from the database, the association property may hold an instance of an NHibernate generated subclass of Item that delegates all method invocations to a different instance of Item that is fetched lazily from the database.

In order to delegate method ( and property ) invocations, these members need to be virtual.

<property name="hibernate.use_proxy_validator">false</property>

cfg.Properties[NHibernate.Cfg.Environment.UseProxyValidator] = "false"

<class name="Item" proxy="ItemInterface">

outer-join
1> outer-join="auto" - The default. NHibernate fetches tha associated object lazily if the associated class has proxying enabled or eagerly using an outer join if proxying is disabled (default).
2> outer-join="true" - NHibernate always fetches the association eagerly using an outer join, even if proxying is enabled. This allow to choose different fetching strategies for different associations to the same proxied class. It's equivalent to fetch="join".
3> outer-join="false" - NHibernate never fetches the association using an out join, even if proxying is disabled. This is useful if you expect the associated object to exist in the second-level cache. This option i equivalent to fetch="select"

<many-to-one name-"item" class="Item" outer-join="true">

<one-to-one name="item" class="Item" constrained="true">
The constrained attribute tells NHibernate that the associated object is required and thus can't be null.

<class name="Item" lazy="true" batch-size="9">

* Collections

collection wrapper

NHibernate needs the wrapper to detect collection modifications

Collection mappings may declare a lazy attribute, an outer-join attribute, neither, or both

  • Neither attribute specified - This option is equivalent to out-join-"false" lazy="false". The collection is fetched from the second-level cache or y an immediate extra SQL SELECT. This option is most useful when the second-level cache is enabled for thsi collection.
  • outer-join="true" - NHibernate fetches the association eagerly using an outer join. At the time of this writing, NHibernate is able to fetch only one collection per SQL SELECT.
  • lazy="true" - NHibernate fetches the collection lazily, when it's first accessed. defaul option.

enable batch fetching for the collection, the batch size doesn't refer to the number of bids in the batch; it refers to the number of collections of bids.

Batch fetching can significantly reduce the number of queries required for hierarchies of objects.

many-to-many associations

link table - relationship table / association table , that holds only the key values of the two associated tables and therefore allows a many-to-many multiplicity.

recommend lazy loading as the default fetching strategy and the NHibernate is limited to one eagerly fetched collection per mapped persistent class.

* Setting the fetch depth

maximum fetch depth: This setting controls the number of outer-joined tables NHibernate uses in a single SQL query.

eager fetching strategies declared in the mapping metadata are effective only if you use retrieval by identifier, user the criteria query API, or naviagete through the object graph manually.

Any HQL query may specify its own fetching strategy at runtime, thus ignoring the mapping defaults.

* Initialzing lazy associations

A proxy or collection wrapper is automatically initialized when any of its methods are invoked.

NHibernateUtil.Initialize()
NHibernateUtil.IsInitialized()

4.4.7 Tuning object retrieval

Enable the NHibernate SQL log.

Step through your application use case by use case , and note how many and what SQL statements NHibernate executes, ... bring down the number (and complexity) of SQL queries for each user case by tuning the default fetching strategies in metadata.

  1. If the SQL statements use join operations that are too complex and slow, set outer-join to false for <many-to-one> associations (this is enable by default). Also try to tune with the global hibernate.max_fetch_depth configuration option, .. best left at a value between 1 and 4.
  2. If too many SQL statements are executed, use lazy="true" for all collection mappings; by default, NHibernate will execute an immediate additional fetch for the collection elements. only one collection property per persistent class may be fetched eagerly. Use batch fetching with value between 3 and 15 to further optimize collection fetching if the given unit of work involve several of the same collections or if you're accessing a tree of parent and child objects.

After optimize all use cases, check every use case again and see if any optimizations had side effects for others.

4.5 Summary

原文地址:https://www.cnblogs.com/zhaorui/p/1749626.html