Search This Blog

Thursday 1 August 2013

Can we mark entity classes as final ?

We have already seen that Hibernate extensively uses proxies. It uses proxies when we call load, it uses proxies for lazily loading associations and collections.But what if our entity class was final ?
A proxy works by replacing our actual class object and being present in its place. A proxy, more specifically put, is a replacement. In Java terms this is possible only if the proxy satisfies the IS A relation.
The proxy IS A entity.
For this to work the proxies created by Hibernate need to extend our entity classes. But if the class is marked final this will not work.
Consider the simple class:
public class Project {
    private Long id;
    private String name;
    // setter getters
}
If I now run a simple load call:
public static void main(String[] args) {
    Session session = HibernateUtil.getSessionFactory().openSession();
    Transaction transaction = session.beginTransaction();
    Project  project = (Project) session.load(Project.class, 1L);
    System.out.println(project.getClass());
    transaction.commit();
    session.close();
}
The logs will indicate that no queries were fired.
class com.test.Project_$$_javassist_0
Also the class of the object is a JavaAssist generated proxy. I now set the class to final and executed the same code. The logs have now changed:
Hibernate: 
    select
        project0_.id as id1_0_,
        project0_.name as name1_0_ 
    from
        projects project0_ 
    where
        project0_.id=?
class com.test.Project
As can be seen final classes work perfectly with Java. However the ability to load the class lazily is lost. What if I made my getters final and left the class as non final ?
public class Project {
    private Long id;
    private String name;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(nullable = false)
    public final Long getId() {
        return this.id;
    }

    public final void setId(Long id) {
        this.id = id;
    }

   @Column(name = "name", length = 255, nullable = false)
    public final String getName() {
        return this.name;
    }


    public final void setName(String name) {
        this.name = name;
    }
If I run my main method the logs are :
class com.test.Project_$$_javassist_0
How did this work. I can understand my final setters working. But the final getters ?
You should also avoid declaring public final methods as this will again limit the ability to
generate proxies from this class. If you want to use a class with public final methods, you
must explicitly disable proxying.
This is from the hibernate docs.
I decided to modify the main method to display the project name:
public static void main(String[] args) {    
    Session session = HibernateUtil.getSessionFactory().openSession();
    Transaction transaction = session.beginTransaction();
    Project project = (Project) session.load(Project.class, 1L);
    System.out.println(project.getClass());
    System.out.println(project.getName());
    transaction.commit();
    session.close();
}
The output is :
class com.test.Project_$$_javassist_0
null
The conclusions from the above is:
  • The proxy was created for a non-final class with final methods.
  • The code for final methods was not modified.
  • When we called the final method getName(), the proxy must have simply delegated the call to the inherited method getName(). As the object was not initialized the method returned null.
  • Since the getter was final, Hibernate was not able to add its smart code that loaded the object when the getter was invoked on the proxy. Thus the logs also do not indicate any select query.
So if you want to work with proxies in Hibernate avoid final class and methods.
(Above code tried with Hibernate 3.6.)

No comments:

Post a Comment