Прокси-объект Hibernate не работает для методов суперкласса

У нас есть веб-приложение, которое использует Hibernate. После обновления кодовой базы до Hibernate 3.6 (с 3.3.2) я обнаружил, что прокси-объекты данных, сгенерированные Hibernate, возвращают правильное значение только для некоторых методов. Кажется, что методы в конкретном классе модели данных работают нормально, но методы в @MappedSuperclass абстрактных суперклассах не работают.

Вот модель данных, которая у нас есть:

@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

}

Это работало нормально в Hibernate 3.3.2, но после обновления до Hibernate 3.6 приложение пошло не так. Следующий тестовый код иллюстрирует проблему:

    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());

Вывод этого кода:

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

Четыре из этих методов вернули неправильные результаты: getId(), getVersion(), getLastUpdateDate() и getCreatedDate() вернули 0 или null. Фактическая строка в базе данных имеет ненулевые/ненулевые значения. Однако getName(), getPath() и, что наиболее любопытно, getSecurityId() работали нормально.

Кто-нибудь может объяснить, почему это происходит? Это фундаментальная проблема с сопоставленными суперклассами или есть другая причина, по которой это может произойти?

Обратите внимание, что объект Form, возвращаемый Hibernate, является прокси-сервером Javassist — при просмотре в отладчике он обычно имеет имя класса, например Form_$$_javassist_15 и т. д.


Обновление:

Эта проблема возникает в Hibernate, а не в Javassist. Я переключил генерацию байт-кода на CGLIB, установив hibernate.bytecode.provider=cglib в hibernate.properties, но получил точно такие же ошибочные результаты с CGLIB на месте (и подтвердил, что CGLIB работает, потому что имя класса, возвращаемое Hibernate, становится Form$$EnhancerByCGLIB$$4f3b4523).

Однако я все еще не приблизился к тому, чтобы понять, почему все идет не так.


person gutch    schedule 13.01.2011    source источник
comment
Возможно, вам нужно подать отчет об ошибке. Если у вас есть тест, который подтверждает это неожиданное поведение, и вы изолировали тест только для Hibernate, то это хороший кандидат для отчета об ошибке. Обычно команда Hibernate бросает вам вызов. Докажите это, предоставив тест :)   -  person chris    schedule 14.01.2011
comment
Я сам виноват, потому что некоторые геттеры и сеттеры были final. Однако я не смог найти предупреждение об этом в документации Hibernate, поэтому могу сообщить о проблеме с документацией. Документация предупреждает о окончательных классах, но не о окончательных методах...   -  person gutch    schedule 14.01.2011


Ответы (1)


Я нашел ответ: некоторые геттеры и сеттеры в суперклассах были помечены final.

Оглядываясь назад, это очевидно... поскольку методы были окончательными, прокси-классы не могли их переопределить. Таким образом, решение состоит в том, чтобы удалить final из любых геттеров и сеттеров в сопоставленных суперклассах.

Вот геттеры и сеттеры, которые были определены на 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;
    }
}

Это объясняет, почему мой тест возвращал securityId правильно, но не возвращал version правильно — getVersion() было final, а getSecurityId() — нет.

Таким образом, не помечайте свои геттеры и сеттеры как final, если Hibernate может попытаться проксировать их!

person gutch    schedule 14.01.2011