In this post I will explain why Hibernate is generating the HHH000179
warning and when ignoring it may introduce bugs in your code.
To understand what this “Narrowing proxy” is all about,
first we must learn about Hibernate proxies.
When we read a value of lazy loaded property or when we call
EntityManager::getReference Hibernate returns a proxy object.
This proxy is an instance of a class that was generated at runtime using
library like Javassit.
For example for a simple entity:
Generated proxy class looks similar to:
TIP: In Hibernate 5.1 you may write generated
proxy classes to disk by putting a breakpoint
method and setting
factory.writeDirectory field to a valid path.
You may want to use a conditional
breakpoint to avoid doing this manually every time a proxy is generated.
The most important point here is that proxy class extends entity class.
Now let’s see what happens when we mix proxies with inheritance.
Given a simple class hierarchy:
When we use EntityManager::getReference to load a Pet we will
get a proxy that extends Pet class because Hibernate does not know yet
whatever our pet is a Cat or a Dog:
We may force Hiberante to query database to load proxied entity
state but that doesn’t change proxy identity:
Even though now Hibernate knows that our pet is a Cat it cannot
change already loaded proxy class definition,
Pet proxy continues to be so.
This may cause you problems because tests like pet instanceof Cat will
fail although pet indeed represents a cat.
There is also a second issue that may come up when working with proxies.
If makeNoise() method would access pet data via field, proxy would not
be notified about that data access and it wouldn’t load data from DB,
causing our method to read an uninitialized field value.
The moral is that we should always use getters and setters
when dealing with entity state.
Now you may think that if we try to load Pet again (after proxy was
initialized), Hibernate will return instance of the Cat entity.
The behavior displayed by Hibernate is slightly different
because of Hibernate first level cache
that prefers returning already
loaded entity instance than creating a new one:
What will happen when we try to explicitly load a Cat entity:
Now we got the famous HHH000179 warning, and Hiberante handled
us unproxied Cat instance.
But why was this warning generated? Because right now we
we have two different object (the proxy and the Cat instance)
in our session that point to exactly the same entity.
Of course the pet proxy is pointing to the cat instance,
and changes applied to e.g. entity instance are reflected in the proxy state:
So you may think that having two representation of the same DB row
in memory is OK,
but the real troubles begin if we do not override equals() and hashCode()
methods properly. This is demonstrated by example:
Fortunately this can be easily fixed by providing equals() implementation
that is based either on primary key or business key equality, for example:
We may also reproduce above behaviour with lazy loading,
you can find an example of how to do this in the attached source code.
Significance in the real world application
Recently I developed a module in an application that was based on huge
in-house framework (Ughhh). This framework let’s call it X
contained some of the entities that we used, but we have no way of
modifying them. The only way to add some fields to an already existing entity
was to extend it (fortunately for us, most entities in X were declared
as base classes with inheritance strategy SINGLE_TABLE).
At the end of this project we had plenty of small class hierarchies
consisting only of super class and a single subclass.
We also had plenty of references from other entities to either
this sup or super classes. As you may expect this was a fertile
ground for Hibernate HHH000179 warnings, and so I devoted a few hours of
my time to figure out what this warning is all about. In our case
providing proper equals() and hashCode() was all that was needed.
But just to sum up I want to present the last, more real world example.
Shipped with framework X:
Shipped with my module:
As you can see legacy class Document is using LegacyUser to refer to
a system user. New class Comment is using ExtendedUser to refer to
a system user.
Without proper equals() implementation we may get into troubles:
And that is all that I wanted to say about HHH000179.
The most important thing that
you should remember from this article is that with
good equals() and hashCode() implementation
HHH000179 warning can be safely ignored.