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

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).id);
    }

    @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 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.

If you liked this article, you might want to subscribe to my newsletter too.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s