How does a JPA Proxy work and how to unproxy it with Hibernate

Imagine having a tool that can automatically detect if you are using JPA and Hibernate properly. Hypersistence Optimizer is that tool!

Introduction

The JPA lazy loading mechanism can either be implemented using Proxies or Bytecode Enhancement so that calls to lazy associations can be intercepted and relationships initialized prior to returning the result back to the caller.

Initially, in JPA 1.0, it was assumed that Proxies should not be a mandatory requirement, and that’s why @ManyToOne and @OneToOne associations use an EAGER loading strategy by default. However, EAGER fetching is bad for performance so it’s better to use the FetchType.LAZY fetching strategy for all association types.

In this article, we are going to see how the proxy mechanism works and how you can unproxy a given Proxy to the actual entity.

Loading a Proxy

The JPA EntityManager defines two ways to load a given entity.

When calling the find method, the entity is going to be loaded either from the first-level cache, second-level cache or from the database. Therefore, the returned entity is of the same type with the declared entity mapping.

On the contrary, when calling the getReference method, the returned object is a Proxy and not the actual entity object type. The benefit of returning a Proxy is that we can initialize a parent @ManyToOne or @OneToOne association without having to hit the database when we justs want to set a Foreign Key column with a value that we already know.

So, when running the following example:

Post post = entityManager.getReference(Post.class, 1L);

PostComment comment = new PostComment();
comment.setId( 1L );
comment.setPost( post );
comment.setReview( "A must read!" );
entityManager.persist( comment );

Hibernate is going to issue a single INSERT statement without needing to execute any SELECT statement:

INSERT INTO post_comment (post_id, review, id) 
VALUES (1, 'A must read!', 1)

While this example underlines when Proxies are useful for writing data, Proxies are very convenient for reading data as well.

Considering we have the following PostComment entity:

@Entity(name = "PostComment")
@Table(name = "post_comment")
public class PostComment {

    @Id
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Post post;

    private String review;

    //Getters and setters omitted for brevity
}

When executing the following test case:

PostComment comment = entityManager.find(
    PostComment.class, 
    1L
);

LOGGER.info( "Loading the Post Proxy" );

assertEquals(
    "High-Performance Java Persistence",
    comment.getPost().getTitle()
);

Hibernate generates the following output:

SELECT pc.id AS id1_1_0_,
       pc.post_id AS post_id3_1_0_,
       pc.review AS review2_1_0_
FROM   post_comment pc
WHERE  pc.id = 1

-- Loading the Post Proxy

SELECT p.id AS id1_0_0_,
       p.title AS title2_0_0_
FROM   post p
WHERE  p.id = 1

The first SELECT statement fetches the PostComment entity without initializing the parent Post association since it was marked with FetchType.LAZY. By inspecting the selected FOREIGN KEY column, Hibernate knows whether to set the post association to null or to a Proxy. If the FOREIGN KEY column value is not null, then the Proxy will only populate the association identifier.

However, when accessing the title attribute, Hibernate needs to issue a secondary SELECT statement to initialize the Post Proxy.

How to unproxy a Proxy object

As we have already seen, by navigating the Proxy object, Hibernate issues the secondary SELECT statement and initializes the association. Hence the Proxy is replaced by the actual entity object.

Considering that the Post entity is mapped as follows:

@Entity(name = "Post")
@Table(name = "post")
public class Post {

    @Id
    private Long id;

    private String title;

    //Getters and setters omitted for brevity

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Post)) return false;

        return id != null && id.equals(((Post) o).getId());
    }

    @Override
    public int hashCode() {
        return 31;
    }
}

When executing the following test case:

Post _post = doInJPA(entityManager -> {
    Post post = new Post();
    post.setId( 1L );
    post.setTitle( "High-Performance Java Persistence" );
    entityManager.persist(post);
    return post;
});

doInJPA(entityManager -> {
    Post post = entityManager.getReference(Post.class, 1L);
    LOGGER.info( 
        "Post entity class: {}", 
        post.getClass().getName() 
    );

    assertFalse( _post.equals(post) );

    assertTrue( 
        _post.equals( Hibernate.unproxy( post ) ) 
    );
});

Hibernate generates the following output:

Post entity class: com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateProxyTest$Post_$$_jvst8fd_0

Because the Proxy object class is a dynamically generated type, so the Proxy post object is not equal to the _post object which is an actual Post class instance.

However, after calling the unproxy method, introduced in Hibernate 5.2.10, the original _post entity and the unproxied post object are equal.

Prior to Hibernate 5.2.10, to unproxy an object without traversing it, you’d have to execute the following logic:

Object unproxiedEntity = null;

if(proxy instanceof HibernateProxy) {
    HibernateProxy hibernateProxy = (HibernateProxy) proxy;
    LazyInitializer initializer = 
        hibernateProxy.getHibernateLazyInitializer();
    unproxiedEntity = initializer.getImplementation();
}

Not very nice, right? Luckily, starting with Hibernate ORM 5.2.10, you can unproxy a Hibernate Proxy with the Hibernate#unproxy utility method:

Object unproxiedEntity = Hibernate.unproxy( proxy );

Much better!

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.

Conclusion

Understanding Hibernate internals can make a difference between an application that barely crawls and one that runs at warp speed. Lazy associations are very important from a performance perspective, and you really have to understand how Proxies work since you will inevitably bump into them on a daily basis.

FREE EBOOK

11 Comments on “How does a JPA Proxy work and how to unproxy it with Hibernate

  1. Hi Vlad, thanks for your work on this great project. I removed all the EAGER loading from our project (and set a batch size default), while it works nicely on collection, it causes bugs on ManyToOne because the proxy is used on many case and my code is no longer working. For instance where I have an abstract variable if I do “myAbstract instanceof ChildOfAbstract” it fails because myAbstract is an instance of a proxy that inherit from the abstract instead of the child, if I cal Hibernate.unproxy then it works.

  2. I have a question about about loading proxies.

    I have a long JPQL querie with several left join fetch of the root entity and all its relations, something as follows:

    em.createQuery(“SELECT DISTINCT ra ” +
    “FROM Book ra ” +
    “LEFT JOIN FETCH ra.mycollection1 di ” +
    “LEFT JOIN FETCH ra.property1 ” +
    “LEFT JOIN FETCH ra.property2 ” +
    “LEFT JOIN FETCH di.collection2 c2 ” +
    “LEFT JOIN FETCH c2.subcollection3 ” +
    “LEFT JOIN FETCH ra.detail d ” +
    “LEFT JOIN FETCH d.property1 ” +
    “LEFT JOIN FETCH d.subcollection1 ” +
    “LEFT JOIN FETCH d.subcollection2 ” +
    “LEFT JOIN FETCH d.subcollection3 ” +
    “WHERE ra.id = :id”,
    Book.class)
    .setParameter(“id”, id)
    .getResultList();

    I’m fetching all the relationships because the application is layered and we need to serialize to json the entity with all its properties loaded.
    But at the time of serialization, some entity properties continue to be proxies even though we are fetching join. That does not make much sense at least to me.
    To solve the problem we are forcing the initialization of all instances with the method:

    “`
    org.hibernate.Hibernate#initialize(Object proxy)

    PS: We are using hibernate 5.1.10.Final,

    So my questions are:

    1 – Why are some properties remain proxies even though I’m doing join fetch?
    2- are we using the most appropriate solution?

    • If you’re using bidirectional associations, some relationships might not be fetched unless the query explicitly included them.

      This is not the best solution anyway. First, you don’t need entities since you want a JSON response. Second, this query will lead to Cartesian Products, hence is suboptimal. Better use multiple queries to fetch DTOs that you correlate in the ResultTransformer. Check out the Fetching chapter in my High-Performance Java Persistence book for more details.

  3. Very interesting article! but I don’t understand the difference between Hibernate.unproxy and Hibernate.initialize. Is it just one returns the object and the other don’t? I was looking at the code and at least that is what I understand.
    I’m having trouble with initializing proxies when the session is closed and I don’t find an elegant way of doing it.

      • You’re right!
        It’s a very big 10 years old system with a lot of bad practices but I’m trying to change that. Probably the book is a good place to start. Thanks!

  4. Proxies, for setting associations, don’t provide a big advantage because I could do:

    PostComment comment = new PostComment();
    comment.setId( 1L );
    comment.setPost( new Post(thePostId) );    // passing a new object with only the I’d
    comment.setReview( "A must read!" );
    entityManager.persist( comment );
    
    • This works with Hibernate, not with other JPA providers. And, after you persist the entity, if you access the parent association, Hibernate will not trigger its initialization.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.