سؤال

We have a web application that uses Hibernate. After upgrading the codebase to Hibernate 3.6 (from 3.3.2) I've found that the proxy data objects generated by Hibernate only return the correct value for some methods. It seems that methods in a concrete data model class work fine, but methods in @MappedSuperclass abstract superclasses are not working.

Here is the data model we have:

@MappedSuperclass
public abstract class DataObject implements Serializable {
    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID", unique = true, columnDefinition = "serial")
    private int id;

    // getter, setter, equals, hashcode implementations here
}

@MappedSuperclass
public abstract class SecuredDataObject extends DataObject {

    @Version
    @Column(name = "Version")
    private int version;

    @Basic
    @Column(name = "SecurityId", nullable = true)
    private Integer securityId;

    // getters, setters here
}

@MappedSuperclass
public abstract class AuditedDataObject extends SecuredDataObject {

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "CreatedDate", nullable = true)
    private Date createdDate;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "LastUpdateDate", nullable = true)
    private Date lastUpdateDate;

    // getters, setters here
}

@Entity
@Table(name = "Form")
public class Form extends AuditedDataObject {

    @Basic
    @Column(name = "Name", length = 200, nullable = false)
    private String name;

    @Basic
    @Column(name = "Path", length = 80, nullable = true)
    private String path;

    // getters, setters, other properties and methods here

}

This worked OK in Hibernate 3.3.2, but after the upgrade to Hibernate 3.6 the application has gone awry. The following test code illustrates the problem:

    int formId = 1234;
    Form form = (Form) sessionFactory.getCurrentSession().load(Form.class, formId);

    System.out.print("id = ");
    System.out.println(formId);
    System.out.print("getId() = ");
    System.out.println(form.getId());
    System.out.print("getSecurityId() = ");
    System.out.println(form.getSecurityId());
    System.out.print("getVersion() = ");
    System.out.println(form.getVersion());
    System.out.print("getLastUpdateDate() = ");
    System.out.println(form.getLastUpdateDate());
    System.out.print("getCreatedDate() = ");
    System.out.println(form.getCreatedDate());
    System.out.print("getName() = ");
    System.out.println(form.getName());
    System.out.print("getPath() = ");
    System.out.println(form.getPath());

The output of that code is:

id = 1234
getId() = 0
getSecurityId() = 182
getVersion() = 0
getLastUpdateDate() = null
getCreatedDate() = null
getName() = Form name here
getPath() = /path/here

Four of those methods have returned incorrect results: getId(), getVersion(), getLastUpdateDate() and getCreatedDate() have returned 0 or null. The actual row in the database has non-zero / non-null values. However getName(), getPath() and most curiously getSecurityId() have worked fine.

Can anyone explain why this is happening? Is it a fundamental problem with mapped superclasses, or is there another reason why this could occur?

Note that the Form object returned by Hibernate is a Javassist proxy - if viewed in a debugger it typically has a class name like Form_$$_javassist_15 etc.


Update:

This problem seems to be occurring in Hibernate, not Javassist. I switched over the bytecode generation to CGLIB by setting hibernate.bytecode.provider=cglib in hibernate.properties, but get exactly the same erroneous results with CGLIB in place (and confirmed CGLIB is working because the class name returned by Hibernate becomes Form$$EnhancerByCGLIB$$4f3b4523).

I'm still no closer to identifying why it's going wrong, though.

هل كانت مفيدة؟

المحلول

I found the answer: some of the getters and setters on the superclasses were marked final.

In hindsight this is obvious... because the methods were final, the proxy classes couldn't override them. So the solution is to remove final from any getters and setters in mapped superclasses.

Here are the getters and setters that were defined on SecuredDataObject:

@MappedSuperclass
public abstract class SecuredDataObject extends DataObject {

    @Version
    @Column(name = "Version")
    private int version;

    @Basic
    @Column(name = "SecurityId", nullable = true)
    private Integer securityId;

    // Note - method IS NOT final
    public Integer getSecurityId() {
        return securityId;
    }

    public void setSecurityId(Integer securityId) {
        this.securityId = securityId;
    }   

    // Note - method IS final
    public final int getVersion() {
        return version;
    }

    public final void setVersion(final int version) {
        this.version = version;
    }
}

It explains why my test was returning securityId correctly but was not returning version correctly — getVersion() was final whereas getSecurityId() was not.

So in summary, don't mark your getters and setters as final if Hibernate might try to proxy them!

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top