Most programmers come into contact with Hibernate proxies when they face the ominous LazyInitializationException. The bad taste is hard to wash away, but Proxies are a necessary “evil” when working with Hibernate. This article will dig into why proxies are important. It will also point out some pitfalls that occur often when working with proxies.
What are proxies?
Proxies are the mechanism that allows Hibernate to break up the interconnected cloud of objects in the database into smaller chunks, that can easily fit in memory.
Let’s look at an example.
PERSON | ||
---|---|---|
ID | NAME | BOSS |
1 | Daan | NULL |
2 | Vincent | 1 |
3 | Eelco | 1 |
4 | Jan | 2 |
5 | Maarten | 3 |
6 | Erik | 3 |
public class Person { private Long id; private String name; private Person boss; private Set employees; }
This is a very simple example. The class on the right is mapped to the table on the left. So what happens if Hibernate loads the row with NAME=’Maarten’? To store the data in memory, Hibernate will create a new Person object and set the columns to the fields. It stores the ID column in the id field and the NAME column in the name field.
Coming to the BOSS column, Hibernate faces a problem: The type of the column is NUMERIC, but the type of the property is Person. This is of course a foreign key, to another row in the PERSON table. Now, Hibernate could simply load the data from the associated row and create a new Person object and store it in the field, but this would cascade eventually to all rows in the table (and possibly other tables in the database), especially taking into account the employees property as well.
Instead of loading the associated row, Hibernate will create a Person object and set the id property to the value found in the BOSS column. This Person object is a specialized Person object, that will load the associated data if necessary. Initially the fields of this new Person object are not set, since the data is not yet loaded. When a method is invoked on the object, Hibernate will fetch the data from the column and populate the object. This is the proxy mechanism.
To add this new behavior (the loading of the data when a method is invoked), Hibernate will create a dynamic subclass of Person using CGLib and add the desired functionality. We need a subclass of Person here, to comply with the type of the boss field.
So what does this look like? Looking at a proxy in a debugger is quite interesting. To the left you find an image of the eclipse debugger displaying a Person object loaded from the database, with the boss property set to a Hibernate proxy. A Hibernate proxy can be easily identified by the class name, which will contain the “$EnhancerByCGLIB$” marker. This is due to the fact that the class is generated at runtime by CGLib.
The proxy class is a subclass of the Person class, so it contains the same fields (as can be seen in the debugger), but it also contains some added fields. The most important is CGLIB$CALLBACK_0, which is set to an instance of CGLIBLazyInitializer, which is a Hibernate class. This is the object that enables the proxy to load the data when needed. It contains a “target” field, that will contain the Hibernate loaded object when the data is loaded.
In the debugger, we can do funny things with this proxy. By clicking the proxy itself, the debugger will invoke the toString() method on the object, effectively loading the data itself, and setting the target field in the LazyInitializer to a Person object with the data loaded. This can change the behavior of applications and unittests severely! The same holds by the way for Hibernate collections that are not yet loaded; clicking it in a debugger will load the collection.
Proxy Pitfall 1: Field Access
Coming to the first pitfall with proxies: When a proxy is loaded, the data is not stored in thr proxy object, but in the “target” object. The fields in the proxy object will remain null forever (or any value that they have been initialized with), since all methods invoked on the proxy will be delegated to the target object.
The main area where this Pitfall arises is the equals(..) method. A very common implementation would be something like this:
@Override public boolean equals(Object obj) { if (obj == null) { return false; } if (obj == this) { return true; } if (!(obj instanceof Person)) { return false; } return name.equals((Person)obj).name); }
checking the name field of the two Person objects to be the same. This will fail however when we invoke it like personObject.equals(personProxy). In the last line of the method we use direct field access on the other object to look at the name field. Since the other object is a proxy, the field will always be null, even if the proxy is in fact encapsulating the personObject itself. Although the equals(..) method is a typical problem area for this pitfall, it is definitely not limited to that. Every method that takes the declaring class as parameter (and thus allows access to fields on that parameter) has the same problem.
To prevent this, use getters and setters in these occasions. The proxy will be loaded when the getter is invoked and the data will be accessible.
Proxy Pitfall 2: instanceOf
A proxy will be a subclass of the field type that is required. Hibernate will look at the type of the field and create a dynamic class with that type, adding the necessary functionality. This means that if the field type is not the actual implementation type, but an interface or superclass, the type of the proxy will be different than the type of the actual object: If the field-type is the superclass of the actual implementation, the proxy-type and the implementation-type will be siblings in the type hierarchy, both extending the superclass.
The proxy object will thus not be an instance of the implementing type, application code depending on this check will fail.
To prevent this pitfall, switch of proxying for the hierarchy, by setting lazy=”false” on the top level class. This will prevent Hibernate from using the proxy mechanism and always load objects of this type or subtypes.
Conclusion
Proxying is one of the most important features of Hibernate. Although it takes some time to see its importance and to work with its features, doing so will help you develop applications with Hibernate more easily. Keeping in mind the two pitfalls that are discussed in this blog will prevent hard to find bugs in your applications.