forked from nhibernate/nhibernate-core
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTwoPhaseLoad.cs
282 lines (249 loc) · 11.2 KB
/
TwoPhaseLoad.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
using System.Diagnostics;
using NHibernate.Cache;
using NHibernate.Cache.Entry;
using NHibernate.Event;
using NHibernate.Impl;
using NHibernate.Intercept;
using NHibernate.Persister.Entity;
using NHibernate.Proxy;
using NHibernate.Type;
using NHibernate.Properties;
using System;
using System.Collections.Generic;
namespace NHibernate.Engine
{
/// <summary>
/// Functionality relating to Hibernate's two-phase loading process,
/// that may be reused by persisters that do not use the Loader
/// framework
/// </summary>
public static partial class TwoPhaseLoad
{
private static readonly INHibernateLogger log = NHibernateLogger.For(typeof(TwoPhaseLoad));
/// <summary>
/// Register the "hydrated" state of an entity instance, after the first step of 2-phase loading.
///
/// Add the "hydrated state" (an array) of an uninitialized entity to the session. We don't try
/// to resolve any associations yet, because there might be other entities waiting to be
/// read from the JDBC result set we are currently processing
/// </summary>
// Since 5.3
[Obsolete("Use the overload without lazyPropertiesAreUnfetched parameter instead")]
public static void PostHydrate(IEntityPersister persister, object id, object[] values, object rowId, object obj, LockMode lockMode, bool lazyPropertiesAreUnfetched, ISessionImplementor session)
{
object version = Versioning.GetVersion(values, persister);
session.PersistenceContext.AddEntry(obj, Status.Loading, values, rowId, id, version, lockMode, true, persister, false, lazyPropertiesAreUnfetched);
if (log.IsDebugEnabled() && version != null)
{
System.String versionStr = persister.IsVersioned ? persister.VersionType.ToLoggableString(version, session.Factory) : "null";
log.Debug("Version: {0}", versionStr);
}
}
/// <summary>
/// Register the "hydrated" state of an entity instance, after the first step of 2-phase loading.
///
/// Add the "hydrated state" (an array) of an uninitialized entity to the session. We don't try
/// to resolve any associations yet, because there might be other entities waiting to be
/// read from the JDBC result set we are currently processing
/// </summary>
public static void PostHydrate(IEntityPersister persister, object id, object[] values, object rowId, object obj, LockMode lockMode, ISessionImplementor session)
{
object version = Versioning.GetVersion(values, persister);
session.PersistenceContext.AddEntry(obj, Status.Loading, values, rowId, id, version, lockMode, true, persister, false);
if (log.IsDebugEnabled() && version != null)
{
System.String versionStr = persister.IsVersioned ? persister.VersionType.ToLoggableString(version, session.Factory) : "null";
log.Debug("Version: {0}", versionStr);
}
}
/// <summary>
/// Perform the second step of 2-phase load. Fully initialize the entity instance.
/// After processing a JDBC result set, we "resolve" all the associations
/// between the entities which were instantiated and had their state
/// "hydrated" into an array
/// </summary>
public static void InitializeEntity(object entity, bool readOnly, ISessionImplementor session, PreLoadEvent preLoadEvent, PostLoadEvent postLoadEvent)
{
InitializeEntity(entity, readOnly, session, preLoadEvent, postLoadEvent, null);
}
/// <summary>
/// Perform the second step of 2-phase load. Fully initialize the entity instance.
/// After processing a JDBC result set, we "resolve" all the associations
/// between the entities which were instantiated and had their state
/// "hydrated" into an array
/// </summary>
internal static void InitializeEntity(object entity, bool readOnly, ISessionImplementor session, PreLoadEvent preLoadEvent, PostLoadEvent postLoadEvent,
Action<IEntityPersister, CachePutData> cacheBatchingHandler)
{
//TODO: Should this be an InitializeEntityEventListener??? (watch out for performance!)
Stopwatch stopWatch = null;
if (session.Factory.Statistics.IsStatisticsEnabled)
{
stopWatch = Stopwatch.StartNew();
}
IPersistenceContext persistenceContext = session.PersistenceContext;
EntityEntry entityEntry = persistenceContext.GetEntry(entity);
if (entityEntry == null)
{
throw new AssertionFailure("possible non-threadsafe access to the session");
}
IEntityPersister persister = entityEntry.Persister;
object id = entityEntry.Id;
object[] hydratedState = entityEntry.LoadedState;
if (log.IsDebugEnabled())
log.Debug("resolving associations for {0}", MessageHelper.InfoString(persister, id, session.Factory));
IType[] types = persister.PropertyTypes;
var collectionToResolveIndexes = new List<int>(hydratedState.Length);
for (int i = 0; i < hydratedState.Length; i++)
{
object value = hydratedState[i];
if (!Equals(LazyPropertyInitializer.UnfetchedProperty, value) && !(Equals(BackrefPropertyAccessor.Unknown, value)))
{
if (types[i].IsCollectionType)
{
// Resolve them last, because they may depend on other properties if they use a property-ref
collectionToResolveIndexes.Add(i);
continue;
}
hydratedState[i] = types[i].ResolveIdentifier(value, session, entity);
}
}
foreach (var i in collectionToResolveIndexes)
{
hydratedState[i] = types[i].ResolveIdentifier(hydratedState[i], session, entity);
}
//Must occur after resolving identifiers!
if (session.IsEventSource)
{
preLoadEvent.Entity = entity;
preLoadEvent.State = hydratedState;
preLoadEvent.Id = id;
preLoadEvent.Persister=persister;
IPreLoadEventListener[] listeners = session.Listeners.PreLoadEventListeners;
for (int i = 0; i < listeners.Length; i++)
{
listeners[i].OnPreLoad(preLoadEvent);
}
}
persister.SetPropertyValues(entity, hydratedState);
ISessionFactoryImplementor factory = session.Factory;
if (persister.HasCache && session.CacheMode.HasFlag(CacheMode.Put))
{
if (log.IsDebugEnabled())
log.Debug("adding entity to second-level cache: {0}", MessageHelper.InfoString(persister, id, session.Factory));
object version = Versioning.GetVersion(hydratedState, persister);
CacheEntry entry =
CacheEntry.Create(hydratedState, persister, version, session, entity);
CacheKey cacheKey = session.GenerateCacheKey(id, persister.IdentifierType, persister.RootEntityName);
if (cacheBatchingHandler != null && persister.IsBatchLoadable)
{
cacheBatchingHandler(
persister,
new CachePutData(
cacheKey,
persister.CacheEntryStructure.Structure(entry),
version,
persister.IsVersioned ? persister.VersionType.Comparator : null,
UseMinimalPuts(session, entityEntry)));
}
else
{
bool put =
persister.Cache.Put(cacheKey, persister.CacheEntryStructure.Structure(entry), session.Timestamp, version,
persister.IsVersioned ? persister.VersionType.Comparator : null,
UseMinimalPuts(session, entityEntry));
if (put && factory.Statistics.IsStatisticsEnabled)
{
factory.StatisticsImplementor.SecondLevelCachePut(persister.Cache.RegionName);
}
}
}
bool isReallyReadOnly = readOnly;
if (!persister.IsMutable)
{
isReallyReadOnly = true;
}
else
{
object proxy = persistenceContext.GetProxy(entityEntry.EntityKey);
if (proxy != null)
{
// there is already a proxy for this impl
// only set the status to read-only if the proxy is read-only
isReallyReadOnly = ((INHibernateProxy)proxy).HibernateLazyInitializer.ReadOnly;
}
}
if (isReallyReadOnly)
{
//no need to take a snapshot - this is a
//performance optimization, but not really
//important, except for entities with huge
//mutable property values
persistenceContext.SetEntryStatus(entityEntry, Status.ReadOnly);
}
else
{
//take a snapshot
TypeHelper.DeepCopy(hydratedState, persister.PropertyTypes, persister.PropertyUpdateability, hydratedState, session);
persistenceContext.SetEntryStatus(entityEntry, Status.Loaded);
}
persister.AfterInitialize(entity, session);
if (session.IsEventSource)
{
postLoadEvent.Entity = entity;
postLoadEvent.Id = id;
postLoadEvent.Persister = persister;
IPostLoadEventListener[] listeners = session.Listeners.PostLoadEventListeners;
for (int i = 0; i < listeners.Length; i++)
{
listeners[i].OnPostLoad(postLoadEvent);
}
}
if (log.IsDebugEnabled())
log.Debug("done materializing entity {0}", MessageHelper.InfoString(persister, id, session.Factory));
if (stopWatch != null)
{
stopWatch.Stop();
factory.StatisticsImplementor.LoadEntity(persister.EntityName, stopWatch.Elapsed);
}
}
private static bool UseMinimalPuts(ISessionImplementor session, EntityEntry entityEntry)
{
return (session.Factory.Settings.IsMinimalPutsEnabled && session.CacheMode != CacheMode.Refresh)
// Use minimal puts also for cacheable lazy properties in order to avoid sending large objects (e.g. an image)
|| (entityEntry.Persister.HasLazyProperties && entityEntry.Persister.IsLazyPropertiesCacheable);
}
/// <summary>
/// Add an uninitialized instance of an entity class, as a placeholder to ensure object
/// identity. Must be called before <tt>postHydrate()</tt>.
/// Create a "temporary" entry for a newly instantiated entity. The entity is uninitialized,
/// but we need the mapping from id to instance in order to guarantee uniqueness.
/// </summary>
// Since 5.3
[Obsolete("Use the overload without the lazyPropertiesAreUnfetched parameter")]
public static void AddUninitializedEntity(EntityKey key, object obj, IEntityPersister persister, LockMode lockMode, bool lazyPropertiesAreUnfetched, ISessionImplementor session)
{
session.PersistenceContext.AddEntity(obj, Status.Loading, null, key, null, lockMode, true, persister, false, lazyPropertiesAreUnfetched);
}
/// <summary>
/// Add an uninitialized instance of an entity class, as a placeholder to ensure object
/// identity. Must be called before <tt>postHydrate()</tt>.
/// Create a "temporary" entry for a newly instantiated entity. The entity is uninitialized,
/// but we need the mapping from id to instance in order to guarantee uniqueness.
/// </summary>
public static void AddUninitializedEntity(EntityKey key, object obj, IEntityPersister persister, LockMode lockMode, ISessionImplementor session)
{
session.PersistenceContext.AddEntity(obj, Status.Loading, null, key, null, lockMode, true, persister, false);
}
// Since 5.3
[Obsolete("Use the overload without the lazyPropertiesAreUnfetched parameter")]
public static void AddUninitializedCachedEntity(EntityKey key, object obj, IEntityPersister persister, LockMode lockMode, bool lazyPropertiesAreUnfetched, object version, ISessionImplementor session)
{
session.PersistenceContext.AddEntity(obj, Status.Loading, null, key, version, lockMode, true, persister, false, lazyPropertiesAreUnfetched);
}
public static void AddUninitializedCachedEntity(EntityKey key, object obj, IEntityPersister persister, LockMode lockMode, object version, ISessionImplementor session)
{
session.PersistenceContext.AddEntity(obj, Status.Loading, null, key, version, lockMode, true, persister, false);
}
}
}