Hibernate 4 ClassCastException при ленивой загрузке, в то время как EAGER работает нормально - PullRequest
7 голосов
/ 07 января 2012

У меня есть следующая корневая сущность СОЕДИНЕННОГО наследования для географических областей (например, континентов, стран, штатов и т. Д.):

@Entity
@Table(name = "GeoAreas")
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class GeoArea implements Serializable
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column
    protected Integer id;

    @Column
    protected String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "parent_id", referencedColumnName = "id")
    protected GeoArea parent;

    ...
}

Как вы можете видеть, географическая область имеет простой автоидентификатор, такой как PK, имя и отношение к своему родителю (ссылка на себя). Пожалуйста, обратите внимание на отношение parent географической области, обозначенное как FetchType.LAZY. В БД parent_id FK NOT NULL делает отношения необязательными. По умолчанию для @ManyToOne установлено optional = true, поэтому сопоставления выглядят правильными.

Подклассы определяют дополнительные свойства и на самом деле не представляют интереса. Данные в БД правильно связаны, так как географические области можно без проблем перечислить через JPQL (с немного другим отображением FetchType.EAGER на parent, см. Конец текста):

Arena list with EAGER loading

Каждая строка является экземпляром:

public class ArenaListViewLine
{
    private final Integer arenaId;
    private final String arenaName;
    private final String arenaLabel;

    private final Boolean hasPostAddress;

    ...

    public ArenaListViewLine(Arena arena, Boolean hasPostAddress)
    {
        // init fields
        ...

        List<String> continentNames = new ArrayList<String>();
        List<String> countryNames = new ArrayList<String>();
        List<String> regionNames = new ArrayList<String>();
        List<String> stateNames = new ArrayList<String>();
        List<String> districtNames = new ArrayList<String>();
        List<String> clubShorthands = new ArrayList<String>();

        // add each geo area to list whose club is a user of an arena (an arena has several usages)
        // in border areas of states two clubs from different states might use an arena (rare!)
        // this potentially adds several states to an entry in the arena list (non in DB yet)
        for ( Usage us : usages )
        {
            Club cl = us.getClub();

            // a club is located in one district at all times (required, NOT NULL)
            District di = cl.getDistrict();

            System.out.println("arena = " + arenaName + ": using club's district parent = " + di.getParent());

            State st = (State)di.getParent(); // ClassCastException here!

            ...
    }

    ...
}

При выполнении запроса списка с родителем, сопоставленным как LAZY, я получаю следующее исключение:

...
Caused by: org.hibernate.QueryException: could not instantiate class [com.kawoolutions.bbstats.view.ArenaListViewLine] from tuple
    at org.hibernate.transform.AliasToBeanConstructorResultTransformer.transformTuple(AliasToBeanConstructorResultTransformer.java:57) [hibernate-core-4.0.0.Final.jar:4.0.0.Final]
    at org.hibernate.hql.internal.HolderInstantiator.instantiate(HolderInstantiator.java:95) [hibernate-core-4.0.0.Final.jar:4.0.0.Final]
    at org.hibernate.loader.hql.QueryLoader.getResultList(QueryLoader.java:438) [hibernate-core-4.0.0.Final.jar:4.0.0.Final]
    at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2279) [hibernate-core-4.0.0.Final.jar:4.0.0.Final]
    at org.hibernate.loader.Loader.list(Loader.java:2274) [hibernate-core-4.0.0.Final.jar:4.0.0.Final]
    at org.hibernate.loader.hql.QueryLoader.list(QueryLoader.java:470) [hibernate-core-4.0.0.Final.jar:4.0.0.Final]
    at org.hibernate.hql.internal.ast.QueryTranslatorImpl.list(QueryTranslatorImpl.java:355) [hibernate-core-4.0.0.Final.jar:4.0.0.Final]
    at org.hibernate.engine.query.spi.HQLQueryPlan.performList(HQLQueryPlan.java:196) [hibernate-core-4.0.0.Final.jar:4.0.0.Final]
    at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1115) [hibernate-core-4.0.0.Final.jar:4.0.0.Final]
    at org.hibernate.internal.QueryImpl.list(QueryImpl.java:101) [hibernate-core-4.0.0.Final.jar:4.0.0.Final]
    at org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:252) [hibernate-entitymanager-4.0.0.Final.jar:4.0.0.Final]
    ... 96 more
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) [:1.7.0_02]
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source) [:1.7.0_02]
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source) [:1.7.0_02]
    at java.lang.reflect.Constructor.newInstance(Unknown Source) [:1.7.0_02]
    at org.hibernate.transform.AliasToBeanConstructorResultTransformer.transformTuple(AliasToBeanConstructorResultTransformer.java:54) [hibernate-core-4.0.0.Final.jar:4.0.0.Final]
    ... 106 more
Caused by: java.lang.ClassCastException: com.kawoolutions.bbstats.model.GeoArea_$$_javassist_273 cannot be cast to com.kawoolutions.bbstats.model.State
    at com.kawoolutions.bbstats.view.ArenaListViewLine.<init>(ArenaListViewLine.java:92) [classes:]
    ... 111 more

Печать до трассировки стека:

13:57:47,245 INFO  [stdout] (http--127.0.0.1-8080-4) arena = Joachim-Schumann-Schule: using club's district parent = com.kawoolutions.bbstats.model.State@2b60693e[id=258,name=Hesse,isoCode=HE,country=com.kawoolutions.bbstats.model.Country@17f9976b[id=88,name=Germany,isoCode=DE,isoNbr=276,dialCode=<null>]]

В печати ясно сказано, что родитель является экземпляром State, но его нельзя передать в State? Понятия не имею ...

При изменении FetchType для GeoArea.parent на EAGER все работает нормально (см. Изображение выше).

Что я делаю не так? Что не так с ленивым намеком?

Спасибо

PS: я использую Hibernate 4.0.0.Final , все сопоставления - стандартные JPA, сервер - JBoss AS 7.

Ответы [ 3 ]

16 голосов
/ 07 января 2012

Проблема с отложенной загрузкой заключается в том, что Hibernate будет динамически генерировать прокси для объектов, которые загружаются лениво.Хотя на первый взгляд это кажется хорошей идеей для реализации отложенной загрузки, разработчик должен осознавать тот факт, что объект может быть Hibernate Proxy.Я думаю, что это хороший пример утечки абстракции.В вашем случае возвращаемое значение вызова di.getParent() - это такой прокси-объект, сгенерированный через библиотеку javassist, которая используется Hibernate.

Такие прокси-серверы Hibernate вызовут проблемы в связи с приведениями, instanceof и вызовамина equals() и hashCode(), если вы не реализовали эти методы для своих сущностей.

Прочитайте этот вопрос и ответ , который показывает, как использовать интерфейс маркера HibernateProxy дляконвертировать прокси в реальный объект.Другой вариант заключается в том, чтобы пойти с энергичной загрузкой в ​​этом случае.

Кстати, пожалуйста, подумайте о том, чтобы отказаться от этой "венгерской нотации"; -)

Отредактировано для решения вопросов из комментария:

Есть ли портативный (JPA) способ добиться этого?

Не то, о чем я знаю.Я бы абстрагировал логику преобразования от другого QA за интерфейсом и предоставил бы альтернативную реализацию noop. Используя JBoss 7 и CDI, вы могли бы переключиться на эту, даже не перекомпилировав.

Может ли этобыть автоматизированным?

Это может быть возможно при использовании AspectJ.Вы можете попробовать написать around рекомендацию для всех обращений к получателям на ваших сущностях, которые возвращают другие сущности.Совет проверит, является ли базовое поле Hibernate Proxy, и если да, примените преобразование, а затем верните результат преобразования.Таким образом, вы всегда получите реальный объект, когда вызывается метод get, но все равно сможете извлечь выгоду из отложенной загрузки.Вы должны тщательно это проверить, хотя ...

2 голосов
/ 28 мая 2017

Несколько вещей, чтобы расширить ответ Роберта Петермейера. Для этой конструкции:

class GeoArea {
    @ManyToOne(fetch = FetchType.LAZY)
    protected GeoArea parent;
}

class State extends GeoArea {}

Поскольку GeoArea.parent является ленивым прокси, при создании этого прокси Hibernate не знает, каким будет реальный класс, загруженный в это поле. Известно только, что это будет подкласс GeoArea. Таким образом, он создает прокси-сервер javaassist для GeoArea класса, который никогда не будет преобразован в State или любой другой подтип GeoArea.

Таким образом, вы можете развернуть прокси для каждого использования instanceof или приведения типов, однако для меня это было не вариант (я сделал ленивые ассоциации в большой системе, чтобы улучшить производительность базы данных - чтобы быть на 100% уверенным, если система все еще работает, мне нужно было бы добавлять распаковку везде, где я могу видеть приведение типа или instanceof). Но у вас есть возможность автоматизировать его с помощью Инструментарий для байт-кода .

Наконец-то я нашел бесшовное решение, чтобы избежать инструментирования байт-кодом - в огромной системе мы все равно не узнаем, что больше не работает, после добавления этой инструментария, а также после добавления кода разворачивания вручную :). Вы можете применить простой трюк, чтобы развернуть прокси вручную в геттере. Вот мой пример:

@MappedSuperclass
public class BaseEntity {

    public BaseEntity getThis() {
        return this;
    }

}

@Entity
public class B extends BaseEntity {

    @ManyToOne(fetch = FetchType.LAZY)
    protected A source;

    public A getSource() {
        return this.source!=null ? (A) this.source.getThis() : null;
    }

    public void setSource(A source) {
        this.source = source;
    }

}   

И здесь - более подробные объяснения проблемы.

0 голосов
/ 29 января 2016

Вы используете JBoss с модулем ClassLoaders.

Основная причина для ClassCastException с прокси состоит в том, что прокси (сгенерированный Hibernate) использует другой экземпляр класса состояний в качестве вызывающей стороны (клиента). Должно быть два экземпляра вашего класса состояний в одном Java JM! JBoss обрабатывает это с модулями и неявными / явными зависимостями импорта / экспорта между модулями.

Итак, вы должны проверить свои прикладные модули или использовать другой сервер приложений со стандартной загрузкой классов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...