PDA

View Full Version : HELP PLEASE - Query Cache / Session Issues


dressina
07-17-2007, 08:33 PM
Hello,
We recently upgraded to NHibernate 1.2-GA and Spring.NET 1.1 M1 and are now having some very confusing issues related to the query cache and NHibernate proxies... but mostly for only 1 class. The issues are sporadic. Nothing has changed with respect to this class during the upgrade.

Since the upgrade, we are now using: OpenSessionInViewModule for session management and the TransactionProxyFactoryObject in our business layer for transaction management. We are not using the HibernateTemplate 100%, as our legacy code requires a lot of work to get it there. However, we have hooked into the HibernateTemplate to give that legacy code access to the ISession (see below).

The issue surfaces when we pass a list of "Property" objects into a query. Property is lazy and the query is cached (see below). We believe it has something to do with a proxy in the query cache and some sort of SessionImpl issue.

Firstly, the stack trace of the issue (we have dealt with the *usual* lazy loading issues before and do not believe this is one of them):

"Could not initialize proxy - the owning Session was closed
" at NHibernate.Proxy.LazyInitializer.Initialize()\r\n at NHibernate.Proxy.CastleLazyInitializer.Intercept(I Invocation invocation, Object[] args)\r\n at CProxyTypeCore_DomainPropertyDomain_NHibernate_Pro xyINHibernateProxy_System_Runtime_SerializationISe rializable2.Equals(Object
obj)\r\n at System.Object.Equals(Object objA, Object objB)\r\n at NHibernate.Engine.TypedValue.Equals(Object obj)\r\n at System.Object.Equals(Object objA, Object objB)\r\n at NHibernate.Util.CollectionHelper.DictionaryEquals( IDictionary a, IDictionary b)\r\n at NHibernate.Cache.QueryKey.Equals(Object other)\r\n at NHibernate.Caches.SysCache.SysCache.Get(Object key)\r\n at NHibernate.Cache.StandardQueryCache.Get(QueryKey key, ICacheAssembler[] returnTypes, ISet spaces, ISessionImplementor session)\r\n at NHibernate.Loader.Loader.GetResultFromQueryCache(I SessionImplementor
session, QueryParameters queryParameters, ISet querySpaces, IType[] resultTypes, IQueryCache queryCache, QueryKey key)\r\n at NHibernate.Loader.Loader.ListUsingQueryCache(ISess ionImplementor
session, QueryParameters queryParameters, ISet querySpaces, IType[] resultTypes)\r\n at NHibernate.Loader.Loader.List(ISessionImplementor
session, QueryParameters queryParameters, ISet querySpaces, IType[] resultTypes)\r\n at NHibernate.Hql.Classic.QueryTranslator.List(ISessi onImplementor session, QueryParameters queryParameters)\r\n at NHibernate.Impl.SessionImpl.Find(String query, QueryParameters parameters, IList results)\r\n at NHibernate.Impl.SessionImpl.Find(String query, QueryParameters parameters)\r\n at NHibernate.Impl.QueryImpl.List()\r\n at
Proc.DataAccess.NHib.NHibernateProcurementDao.GetO penTransactionHeaderPage(Int32
recordIndex, Int32 pageSize, TransactionHeaderFields sortField, Boolean isDescending, TransactionType TrType, IList StatusList, IList
PropertyList) in
F:\\work\\\\Proc.DataAccess\\NHibernate\\NHibernat eProcDao.cs:line
5115" string


Our Spring / NHibernate Configs are:

<!-- Transactional Proxies For All Business Managers -->
<object id="TxProxyConfigurationTemplate" abstract="true"
type="Spring.Transaction.Interceptor.TransactionProxyFac toryObject, Spring.Data">
<property name="PlatformTransactionManager" ref="HibernateTransactionManager"/>
<property name="TransactionAttributes">
<name-values>
<!-- Add common methods across your services here -->
<add key="Update*" value="PROPAGATION_REQUIRED"/>
<add key="Delete*" value="PROPAGATION_REQUIRED"/>
<add key="Create*" value="PROPAGATION_REQUIRED"/>
<add key="Get*" value="PROPAGATION_REQUIRED,readOnly"/>
<add key="Find*" value="PROPAGATION_REQUIRED,readOnly"/>
</name-values>
</property>
</object>



<!-- Database and NHibernate Configuration -->
<db:dbProvider id="DbProvider" provider="System.Data.SqlClient"
connectionString="Data Source=${db.datasource};Initial Catalog=${db.database};Integrated Security=${db.integrated_security};Persist Security Info=false;Min Pool Size=2"/>

<object id="SessionFactory" type="Core.DataAccess.NHib.SammsLocalSessionFactoryObjec t, Tradewinds.Samms.Core.DataAccess">
<property name="DbProvider" ref="DbProvider"/>
<property name="MappingAssemblies">
<list>
<value>Core.DataAccess</value>
<value>Inventory.DataAccess</value>
</list>
</property>
<property name="HibernateProperties">
<dictionary>

<entry key="hibernate.connection.provider"
value="NHibernate.Connection.DriverConnectionProvider"/>

<entry key="hibernate.dialect"
value="NHibernate.Dialect.MsSql2005Dialect"/>

<entry key="hibernate.connection.driver_class"
value="NHibernate.Driver.SqlClientDriver"/>

<entry key="hibernate.use_outer_join"
value="true"/>

<entry key="hibernate.cache.use_query_cache"
value="true"/>

<entry key="hibernate.cache.provider_class"
value="NHibernate.Caches.SysCache.SysCacheProvider,NHiber nate.Caches.SysCache"/>

<entry key="expiration"
value="300"/>

<entry key="hibernate.bytecode.provider"
value="lcg"/>

</dictionary>
</property>
<property name="EntityInterceptor" ref="RevisionUpdateInterceptor" />

</object>


<object id="HibernateTransactionManager"
type="Spring.Data.NHibernate.HibernateTransactionManager , Spring.Data.NHibernate12">
<property name="DbProvider" ref="DbProvider"/>
<property name="SessionFactory" ref="SessionFactory"/>
<property name="EntityInterceptor" ref="RevisionUpdateInterceptor" />
</object>

<object id="HibernateTemplate" type="Spring.Data.NHibernate.Generic.HibernateTemplate">
<property name="SessionFactory" ref="SessionFactory" />
<property name="TemplateFlushMode" value="Auto" />
<property name="CacheQueries" value="true" />
<property name="EntityInterceptor" ref="RevisionUpdateInterceptor" />
</object>


Property Mapping file:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="Core.Domain.Property, Core.Domain" table="PROPERTY">
<cache usage="read-write"/>
<id name="Id" type="Int32" unsaved-value="0">
<column name="PRP_ID" sql-type="int" not-null="true" unique="true" index="PK_PROPERTY"/>
<generator class="native" />
</id>
<property name="Code" type="String">
<column name="PRP_CODE" length="20" sql-type="nvarchar" not-null="true"/>
</property>
</class>
</hibernate-mapping>


And the DAO Code:

public IPage GetFilteredOpenTransactionHeaderPage(int recordIndex,
int pageSize,
TransactionHeaderFields sortField,
bool isDescending,
TransactionType TrType,
IList StatusList,
IList PropertyList,
ProcHeader criteria,
string reqDateSeachType,
string createDateSeachType)
{
IPage page = null;
IList records = null;
ISession session = null;
IQuery recordQuery = null;
...
session = PersistenceManager.OpenSession();

...
// This is the list of properties that is involved in the exception
recordQuery.SetParameterList("propList", PropertyList);

...
recordQuery.SetParameter("transType", TrType);
...
// This causes the exception
records = recordQuery.SetFirstResult(recordIndex)
.SetMaxResults(pageSize + 1)
.SetCacheable(true)
.SetCacheRegion(CacheRegions.CACHE_REGION_TRANSACT IONS)
.List();

return page;
}


PersistenceManager is our hook into the HibernateTemplate:

public sealed class PersistenceManager
{
private ISessionFactory sessionFactory;

public PersistenceManager(ISessionFactory sessionFactory)
{
this.sessionFactory = sessionFactory;
}

public ISession OpenSession()
{
return SessionFactoryUtils.GetSession(sessionFactory, true);
}
}

where SessionFactory is:

return new PersistenceManager(hibernateTemplate.SessionFactor y);


Thank you very much in advance,
Aaron

dressina
07-17-2007, 09:01 PM
I forgot to mention: this issue goes away with any of the following:
1. disable query cache
2. do not cache this particular query
3. make Property NOT lazy

Thanks again,
Aaron

steinard
07-17-2007, 11:41 PM
Hi!

This is just a wild guess, but can you verify that the proxy somehow haven't been cached in a prior fetch? If you have loaded some entity E earlier and that entity holds the property P which is lazy (meaning that E has a proxy object for property P) then the cache will return the proxy instead of the real deal when loading a new entity that also references property P.

You can test this in your DAO method GetFilteredOpenTransactionHeaderPage by checking if any of your retrieved instances is not initialized and if they are, you could evict the proxy and reload it:


if(!NHibernateUtil.IsInitialized(proxy)
{
log.Debug("Found an uninitialized cached proxy: ["+proxy+"]");
// get the session. Call evict(proxy). Reload proxy. Update owning entity...
}


There is a discussion here: http://forum.hibernate.org/viewtopic.php?t=977139

Hope you solve your problem soon,
Steinar.

dressina
07-18-2007, 12:51 AM
Thanks for the suggestion Steinard... we have checked and the proxies are being used to generate the first query (the one that is cached).

So, my question is this: Is there an issue with having uninitialized proxies stored as criteria for a cached query? Again I think it is important to note that we did not have any problems prior to the upgrade.

Thanks again,
Aaron

steinard
07-18-2007, 09:18 AM
Hi!

I'm not sure if this is related to the upgrade, but I'm quite sure that the owning session has been closed, and that you are accessing a proxy since you get the LazyInitialixationException. There are only two classes in the NHibernate libs that throws LIE and both of these classes resolve proxies.

I forgot to mention: this issue goes away with any of the following:
1. disable query cache
2. do not cache this particular query
3. make Property NOT lazy
Yes, changing your chache options or your fetch-strategies should of course change what is cached and that is probably the reason why it works when you modify these settings above. I also think this indicates that you are getting chached proxies when you expect fully initialized objects. Have you checked if there have been any work done on the query cache lately?

In the thread I referenced, someone has loaded entity A which lazily references entity B. When loading entity C completly (no proxies), and entity C has a collection and that collection contains the lazy-loaded B instance among other instances. Then, as I understood it, the collection members will all be fully initialized except the lazy entity B (beacuse it is in the cache, and NHibernate does not know wether a chached instance is the real object or just the proxy).

we have checked and the proxies are being used to generate the first query (the one that is cached). Yes, this might be the problem. The best thing I believe you could do would be to debug the problem by trying to find the proxies before you close your NH-session. You debug this by looping through the newly fetched object graph to find out which entities has not been fully initialized. When you find the proxies, then you might have enough information to solve the problem (optimizing your fetch-plan or preventing cacheing of certain entities or other approaches). I have a similar problem and I solved it by creating a method for checking that the fetched entity is not a proxy or that any of the properties references a proxy. I use AOP here, so I simply decorate those DAO-methods (currently only one) I want to apply this check on (as it will delay my fetching by looping the newly fetched object graph).

Another thing, you might open a case on NHibernate's Jira saying that it should be possible to configure wether proxies are allowed to go into the cache or not. This would of course impose some overhead on fetching with cache turned on.

Cheers,
Steinar.

dressina
07-18-2007, 04:30 PM
Thanks again for the help Steinard. I guess I am still finding it hard to believe that the cache (second level / query cache... not session cache) is incapable of handling lazy proxies - I would have imagined that a lazy proxy stored in a cache would associate to the "current" session to initialize itself the same way it would have if you navigated the object graph directly after loading it.

Any other thoughts?
-Aaron

steinard
07-18-2007, 11:39 PM
Hi!

No, I'm running out of ideas here. I'm no expert on NHibernate. In our project we are creating winforms in a three tiered architecture, thus all loaded entities will become detached. So whenever we need to load collections or entities lazily on demand, we have to handle the LazyInitializationException or prevent that exception from occurring. Thus I've spent some time tackling the LazyInitializationException. Do you have any theories?

I would have imagined that a lazy proxy stored in a cache would associate to the "current" session to initialize itself the same way it would have if you navigated the object graph directly after loading it.

I'm not sure, but I've always had the impression that the the identity map has no knowledge about sessions. As far as the identity map knows, there could be several sessions open simultaneously, how could it then just assign it's content to any session? I have always assumed that the objects stored in the identity map knew about it's own session and if that session has become closed then the object must be reattached to some other open session.

By looking at the exception you received then it is clear that the owning session is closed when the proxy tries to initialize itself. As I have understood it, you must re-attach your detached object to the current session manually.


session.Lock(owningEntity, LockMode.Read);


If you do that, then it should be able to initialize itself.

Cheers,
Steinar.