@Proxy deprecated in Hibernate 6.2

Hello,

Since Hibernate 6.2 the @Proxy annotation is deprecated with the comment: This annotation is almost never useful.

I have a rather large domain model on Hibernate 5.5 where one particular entity has many subclasses (and some have themselves subclasses) mapped with @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
This is a bit like: Wolf extends Mammal extends Vertebrate extends Animal
and: Snail extends Mollusk extends Animal

Now when I have loaded these objects I often get uninitialized proxies (which is fine) but these proxies are instances of Animal but I’d like to get instances of the relevant class i.e., Wolf or Snail because if want to be able to write things like:

if (animal instanceof Snail snail) {
   return snail.getShell();
} else {
   return null;
}

I understand why Hibernate creates a proxy instance of Animal (for instance when there’s a @ManyToOne association typed as Animal) and I also get that I could declare an abstract getShell() method on my Animal class, but to get around this problem I have annotated the class with @Proxy(lazy = false).
Doing that I lose some lazy loading, but I end up with a saner design.

To be clear, the idea here is not to get around a LazyInitializationException by disabling proxies: what I need is to get a an object (possibly a proxy) of the correct class.

When trying to upgrade from 5.5 to 6.2 I’m running into two issues that both disappear when removing the @Proxy(lazy = false) annotations:

So I’m wondering what the Hibernate team has planned: is the intent to drop the support for @Proxy(lazy = false) down the road?
If so, is there a replacement planned to support the case when you need the objects to be of the correct class?

I think that @Proxy(lazy = false) so far only resulted in making every association that refers to that type implicitly EAGER to avoid the problem of creating proxies of the “wrong type”, but that hurts performance.

I think that one possible solution to this problem could be to add the discriminator to the association side, next to the FK column, but that’s a new feature and will take a while, though I think that would be a good replacement.

One simple way to get around this would be to add a method to your top type:

boolean isInstance(Class<?> clazz) {
    return clazz.isInstance(this);
}

This will trigger lazy initialization if necessary, but is still easy to use.

Thanks for the answer,
I agree that @Proxy(lazy = false) carries a performance penalty due to the eager fetching.

The proposed solution to add a discriminator next to the FK column sounds good, alternatively could the discriminator be part of the ID itself?

Another solution would be for Hibernate to let the user plug some kind of resolver that, given an ID, returns the correct type.

Something like:

public interface CustomEntityTypeResolver {
	Class<?> resolveClass(Serializable id);
}

Also, the entity might be in the second-level cache. In my testing the correct type is returned when that happens, so it might be a workable solution.

The class.isInstance(...) test does not seem to work unfortunately, because the object is of class Animal$HibernateProxy$xyz, so both these tests are false:

  • Snail.class.isInstance(animal)
  • animal instanceof Snail

The reliable method I have found is to use:
Hibernate.isInstance(animal, Snail.class);
and:
Snail snail = (Snail) Hibernate.unproxy(animal);

The proposed solution to add a discriminator next to the FK column sounds good, alternatively could the discriminator be part of the ID itself?

You can map the discriminator as part of an embedded id, but this information will not be used for proxies yet.

Another solution would be for Hibernate to let the user plug some kind of resolver that, given an ID, returns the correct type.

That is certainly an option, but how would you determine the type based on the id?

Also, the entity might be in the second-level cache. In my testing the correct type is returned when that happens, so it might be a workable solution.

The thing is, you usually can’t rely on this.

The class.isInstance(...) test does not seem to work unfortunately, because the object is of class Animal$HibernateProxy$xyz , so both these tests are false

Every method call to a proxy except for the getId method should delegate to the implementation and hence initialize the proxy. Can you show me what fails? The code responsible for this should be org.hibernate.proxy.pojo.BasicLazyInitializer#invoke

Another solution would be for Hibernate to let the user plug some kind of resolver that, given an ID, returns the correct type.

That is certainly an option, but how would you determine the type based on the id?

I’m thinking of an application-level service initialized at startup and kept up-to-date whenever a new entity is created.
In my case theses entities are not created often and there are not so many, so essentially I could implement this by:

  • Initializing an ID/class map at startup with an SQL query and then locating the java class from the discriminator using the metamodel
  • Whenever a new entity is created, add the class to the map
  • In case (wouldn’t happen in my case) there’s entry for an ID, throw an exception or possibly run a separate SQL query

The class.isInstance(...) test does not seem to work unfortunately, because the object is of class Animal$HibernateProxy$xyz , so both these tests are false

Every method call to a proxy except for the getId method should delegate to the implementation and hence initialize the proxy. Can you show me what fails? The code responsible for this should be org.hibernate.proxy.pojo.BasicLazyInitializer#invoke

Sorry, I did not get that your suggestion was an instance method. Indeed this is delegated to the implementation and returns the correct class :+1:

I’m thinking of an application-level service initialized at startup and kept up-to-date whenever a new entity is created.
In my case theses entities are not created often and there are not so many, so essentially I could implement this by:

Instead of doing this yourself, you could just activate the second level cache for that entity then, which should result in more or less the same behavior :wink:

Yes, putting the entity in the 2nd level cache would be a good solution!

Now, what would it entail to get this hypothetical CustomEntityTypeResolver added to the Hibernate API?
I only know Hibernate well enough to be dangerous but I can offer to contribute :slight_smile: however I’m not sure how high would be the bar for that kind of enhancement.
I suppose that this kind of change would need to be discussed internally in the Hibernate team.

Let me know if there’s anything I can do to get the ball rolling!

I don’t think that CustomEntityTypeResolver would be a good solution. Allowing the discriminator to be part of the FK side makes much more sense IMO.

The best way to start contributing if you want to do that, would be to create a JIRA issue. Next step would be to discussing this on Zulip with the rest of the team and help us write tests that assert the intended behavior. When we have that, we can prototype an implementation.

Unrelated to the question but to the title: @Proxy(lazy = false) is the only way to have sealed classes as entities. If that annotation is removed that would mean losing support for that type of class.

Don’t worry about this, we’re going to add a replacement for this [HHH-17818] - Hibernate JIRA

This is great news, thank you very much for improving this (instead of removing it)!