Skip to content

Commit

Permalink
Some comments added.
Browse files Browse the repository at this point in the history
  • Loading branch information
mederly committed Jan 22, 2016
1 parent 220bb2f commit 987b846
Showing 1 changed file with 40 additions and 26 deletions.
Expand Up @@ -24,56 +24,70 @@
import org.hibernate.type.Type;

/**
* Hibernate seems to load lazy ManyToOne associations when merge() or whole object fetch (via Criteria API) is called.
* For "target" property in REmbeddedReference, RObjectReference and RCertCaseReference this is very unfortunate,
* as it yields fetching the while RObject instance, which means joining approx. 20 tables - a huge overhead.
* Hibernate seems to load ManyToOne association targets when doing "merge" or "object load" operation,
* even though they are declared as LAZY.
*
* I've found no easy way how to prevent this. One almost-working attempt was to declare the field as lazy
* in the "no-proxy" way. However, it has 2 drawbacks:
* 1) there are still SELECTs executed in such situations (perhaps one per each association)
* 2) it required build-time instrumentation, which failed on RObject for unclear reasons
* Even if #2 would be solved, problem #1 is still there, leading to performance penalties.
* For recently-added "target" association in REmbeddedReference, RObjectReference and RCertCaseReference
* this is very unfortunate, as it leads to fetching the whole RObject instance (the target of the association),
* which means joining approx. 20 tables - a huge overhead. For example, this adds an extra 50 milliseconds
* in case of locally-run PostgreSQL database.
*
* So here is the hack.
* What is interesting, we don't use values of this association in java objects in any way.
* We created this association only to allow the use of left outer joins of referenced objects in HQL queries (see below).
*
* We provide a custom persister - for every entity that has any references, even if embedded via REmbeddedReference.
* It simply erases any traces of "misbehaving" ManyToOne association values, replacing them by null values.
* I've found no easy way how to prevent automatic loading of referenced objects.
* (Unsuccessful attempts are listed below.)
*
* So here is the solution - quite an ugly hack.
*
* We provide a custom persister for every entity that contains the "target" association. (It is the majority
* of entities, mainly because REmbeddedReference is used in many objects.) The persister modifies the hydrate() method,
* responsible for translation of database rows into Java entity objects. More specifically, after invoking
* original hydrate() method, we erase any traces of "target" ManyToOne association values, replacing them by nulls.
* Of course, this means that such values will not be accessible within hydrated POJOs. But we don't care: we
* do not need them there. The only reason of their existence is to allow their retrieval via HQL queries.
*
* Drawbacks/limitations:
* 1) As mentioned, association values cannot be retrieved from POJOs. Only by SQL/HQL.
* 2) Custom persister has to be manually declared on all entities that directly or indirectly contain references;
* otherwise the performance when getting/merging such entities will be (silently) degraded.
* 1) As mentioned, association values cannot be retrieved from POJOs. Only by HQL.
* 2) Custom persister has to be manually declared on all entities that directly or indirectly contain
* the "target" association; otherwise the performance when getting/merging such entities will be (silently) degraded.
* 3) When declaring the persister, one has to correctly determine its type (Joined/SingleTable one).
* One can help himself by looking at which persister is used by hibernate itself
* (e.g. sessionFactoryImpl.getEntityPersisters).
* 4) Current implementation is quite simple minded in that it expect that the name of association is
* "target". This might collide with other ManyToOne associations with that name.
* (e.g. by calling sessionFactoryImpl.getEntityPersisters).
* 4) Current implementation is quite simple minded as it expects that the name of association is
* "target". This might collide with other ManyToOne associations with that name. (Although currently there are none.)
* In future we could create some annotation to select associations to be "killed".
* 5) It is unclear if this solution would work with persisters with batch loading enabled. Fortunately,
* we currently don't use this feature in midPoint.
*
* Alternative solutions:
* 1) "no-proxy" as mentioned above (results in useless SELECTs + requires build time instrumentation)
* 2) Elimination of associations altogether. It possible to fetch targets in HQL even without them by
* simply listing more (unrelated) classes in FROM clause, but this leads to inner, not left outer joins.
* The effect is that targets with null or non-existent OIDs are not included in the result.
* Explicit joins for unrelated classes cannot be used for now (https://hibernate.atlassian.net/browse/HHH-16).
* 1) One almost-working attempt was to declare the field as lazy in the "no-proxy" way
* (@LazyToOne(LazyToOneOption.NO_PROXY)). However, it has 2 drawbacks:
* A) there are still SELECTs executed in such situations (perhaps one per each association)
* B) it required build-time instrumentation, which failed on RObject for unclear reasons
* Even if B would be solved, problem A is still there, leading to performance penalties.
* 2) Elimination of associations altogether. It possible to fetch targets in HQL even without the associations by
* simply listing more (unrelated) classes in FROM clause. However, this leads to inner, not left outer joins.
* The effect is that objects that have targets with null or non-existent OIDs are not included in the result,
* which is obviously unacceptable. Explicit joins for unrelated classes cannot be used for now
* (https://hibernate.atlassian.net/browse/HHH-16).
* 3) Using "target" associations only for references that need them. This feature is currently used only
* for certifications, so it would be possible to create RResolvableEmbeddedReference and use it for
* certification case entity. (Plus at some other places.) The performance degradation would be limited
* to these cases only. Also, other modules would not be able to employ displaying/selecting/ordering by
* to these entities. However, other modules would not be able to employ displaying/selecting/ordering by
* e.g. referenced object names (or other properties).
*
* If this hack would work, it could be kept here. If not, alternative #3 should be employed. After HHH-16 is
* provided, we should implement alternative #2.
* If it would turn out that no harm is caused by this hack, it could be kept here.
* If not, alternative #3 should be employed. After HHH-16 is provided, we should implement alternative #2.
*
* @author mederly
*/
public class MidpointPersisterUtil {

private static final Trace LOGGER = TraceManager.getTrace(MidpointPersisterUtil.class);

public static final String ASSOCIATION_TO_REMOVE = "target";

public static void killUnwantedAssociationValues(String[] propertyNames, Type[] propertyTypes, Object[] values) {
killUnwantedAssociationValues(propertyNames, propertyTypes, values, 0);
}
Expand All @@ -95,7 +109,7 @@ private static void killUnwantedAssociationValues(String[] propertyNames, Type[]
ComponentType componentType = (ComponentType) type;
killUnwantedAssociationValues(componentType.getPropertyNames(), componentType.getSubtypes(), (Object[]) value, depth+1);
} else if (type instanceof ManyToOneType) {
if ("target".equals(name)) {
if (ASSOCIATION_TO_REMOVE.equals(name)) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("{}- killUnwantedAssociationValues KILLED #{}: {} (type={}, value={})",
StringUtils.repeat(" ", depth), i, name, type, value);
Expand Down

0 comments on commit 987b846

Please sign in to comment.