Does a final method prevent Hibernate from creating a proxy for such an entity?
Hibernate uses proxies 开发者_StackOverflow社区to enable lazy loading of collections and even single-ended associations. According to Hibernate's (3.6.5) reference documentaion (Section 21.1.3, Single-ended association proxies), such a proxy can't be constructed by Hibernate if it contains "any final methods".
My question is, does this restriction apply to getters/setters of persistent fields only or really to any method in an entity class? So, does a method like this one:public final String toString() {
return this.getClass().getSimpleName() + id;
}
really prevent the creation of a (CGLIB or Javassist) proxy for this entity? Does it matter if field-based or property access is used? Since CGLIB was replaced by Javassist, does this provide any more features in this direction?
I like to use inheritance in my entity hierarchy and hence the requirement to define some final methods, for example, in the base class to prevent subclasses from overriding those methods.
thanks in advance!
By the help of the Hibernate mailing list (thanks Emmanuel Bernardt!) I'm able to answer my own question, the summary is:
Final methods do not prevent Hibernate from creating a proxy in general but unless those methods don't use any state of the entity this is highly inadvisable.
Some background information: Hibernate doesn't use bytecode enhancement neither with cglib nor with Javassist so, in order for a proxy to initialize its target entity lazily it has to intercept any method which may use the state of that target entity. Now it's perfectly ok to have a final method like this
public final doSomething(String a, Integer b ) {
// do complicated stuff using only a and b (no instance members accessed!)
}
but as soon as this method uses any persistent field directly or through another instance method this would bypass the proxy and thus lead to unexpected behaviour.
As a sidenote this is the same reason why you should not access fields of other instances directly, for example in an entities equals
method:
// XXX bad code!
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Profile)) return false;
Profile profile = (Profile) o;
// XXX this bypasses a possible proxy, use profile.getName() instead!
return (name == null ? profile.name == null : name.equals(profile.name));
}
I'm pretty sure that, as the reference says, it applies to any method. Indeed, a proxy, in my understanding, is nothing more than a subclass of the entity which has no state except for the ID of the entity initially, and which delegates every method call to an actual instance of the entity class once initialized. It thus has to override all of the methods in order to
- initialize itself if necessary
- delegate the call to the actual instance method
Inspired by this question I have created a plugin for Intellij IDEA, that solves this problem. Here it is:
https://plugins.jetbrains.com/plugin/7866
The simple description is this:
Hibernate silently fails in certain situations, leading to bugs which are difficult to track down. This plugin helps finding and fixing some of these problems. Under Settings > Inspections > Hibernate inspections it adds the following inspections: • Persisted class is final; • Final method of a persisted class uses direct field access.
==============================================
A side note:
As @BartvanHeukelom said in the comments, the fact that final methods cannot be proxied can be used as an asset: You may then get the id of the entity from the getter, without initializing the proxy and loading its fields. This is the code I use:
@SuppressWarnings ("AccessingFieldFromAFinalMethodOfPersistedClass")
public final Id getId() {
if (this instanceof HibernateProxy) return (Id)((HibernateProxy)this).getHibernateLazyInitializer().getIdentifier();
else return id;
}
Please note the @SuppressWarnings
. This is necessary only if you use IntelliJ IDEA with my plugin.
精彩评论