Hibernate/JPA: QueryException, когда hibernate генерирует метамодель для @ElementCollection

Я пытаюсь сохранить @ElementCollection внутри объекта с помощью hibernate 4.3.1.Final. Сущность выглядит так:

import javax.persistence.*;
import java.util.Map;
@Entity
public class MyEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;
    @ElementCollection
    private Map<CompositeKey, CompositeValue> map;

    @Embeddable
    public static class CompositeKey {
        @Basic
        private Integer key1;
        @Basic
        private Integer key2;
    }

    @Embeddable
    public static class CompositeValue {
        @Basic
        private Integer val1;
        @Basic
        private Integer val2;
    }
}

Hibernate правильно генерирует схему:

create table MyEntity (id integer not null, primary key (id)) ENGINE=InnoDB;
create table MyEntity_map (MyEntity_id integer not null, val1 integer, val2 integer, key1 integer, key2 integer, primary key (MyEntity_id, key1, key2)) ENGINE=InnoDB;
create table ReferencedEntity (id integer not null, primary key (id)) ENGINE=InnoDB;
alter table MyEntity_map add constraint FK_mhu8q8dtieguddm0w4gxfwhnc foreign key (MyEntity_id) references MyEntity (id);

Сгенерированная схема

Но когда я меняю CompositeKey на ссылку на другой Entity, я получаю QueryException, когда hibernate генерирует метамодель при запуске.

Измененный код:

@Embeddable
public static class CompositeKey {
    @ManyToOne
    private ReferencedEntity key1;
    @Basic
    private Integer key2;
}

Упомянутый Entity:

import javax.persistence.*;
@Entity
public class ReferencedEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;
}

Исключение:

Caused by: org.hibernate.QueryException: could not resolve property: key1 of: component[val1,val2]
    at org.hibernate.persister.entity.AbstractPropertyMapping.propertyException(AbstractPropertyMapping.java:83) ~[AbstractPropertyMapping.class:4.3.1.Final]
    at org.hibernate.persister.entity.AbstractPropertyMapping.toColumns(AbstractPropertyMapping.java:98) ~[AbstractPropertyMapping.class:4.3.1.Final]
    at org.hibernate.persister.collection.AbstractCollectionPersister.toColumns(AbstractCollectionPersister.java:1625) ~[AbstractCollectionPersister.class:4.3.1.Final]
    at org.hibernate.loader.plan.build.internal.spaces.CompositePropertyMapping.toColumns(CompositePropertyMapping.java:124) ~[CompositePropertyMapping.class:4.3.1.Final]
    at org.hibernate.loader.plan.build.internal.spaces.CompositeQuerySpaceImpl.toAliasedColumns(CompositeQuerySpaceImpl.java:52) ~[CompositeQuerySpaceImpl.class:4.3.1.Final]
    at org.hibernate.loader.plan.build.internal.spaces.JoinImpl.resolveAliasedLeftHandSideJoinConditionColumns(JoinImpl.java:79) ~[JoinImpl.class:4.3.1.Final]
    at org.hibernate.loader.plan.exec.internal.LoadQueryJoinAndFetchProcessor.addJoins(LoadQueryJoinAndFetchProcessor.java:261) ~[LoadQueryJoinAndFetchProcessor.class:4.3.1.Final]
    at org.hibernate.loader.plan.exec.internal.LoadQueryJoinAndFetchProcessor.renderEntityJoin(LoadQueryJoinAndFetchProcessor.java:193) ~[LoadQueryJoinAndFetchProcessor.class:4.3.1.Final]
    at org.hibernate.loader.plan.exec.internal.LoadQueryJoinAndFetchProcessor.renderJoin(LoadQueryJoinAndFetchProcessor.java:158) ~[LoadQueryJoinAndFetchProcessor.class:4.3.1.Final]
    at org.hibernate.loader.plan.exec.internal.LoadQueryJoinAndFetchProcessor.processQuerySpaceJoin(LoadQueryJoinAndFetchProcessor.java:137) ~[LoadQueryJoinAndFetchProcessor.class:4.3.1.Final]
    at org.hibernate.loader.plan.exec.internal.LoadQueryJoinAndFetchProcessor.processQuerySpaceJoins(LoadQueryJoinAndFetchProcessor.java:132) ~[LoadQueryJoinAndFetchProcessor.class:4.3.1.Final]
    at org.hibernate.loader.plan.exec.internal.LoadQueryJoinAndFetchProcessor.processQuerySpaceJoin(LoadQueryJoinAndFetchProcessor.java:138) ~[LoadQueryJoinAndFetchProcessor.class:4.3.1.Final]
    at org.hibernate.loader.plan.exec.internal.LoadQueryJoinAndFetchProcessor.processQuerySpaceJoins(LoadQueryJoinAndFetchProcessor.java:132) ~[LoadQueryJoinAndFetchProcessor.class:4.3.1.Final]
    at org.hibernate.loader.plan.exec.internal.LoadQueryJoinAndFetchProcessor.processQuerySpaceJoins(LoadQueryJoinAndFetchProcessor.java:113) ~[LoadQueryJoinAndFetchProcessor.class:4.3.1.Final]
    at org.hibernate.loader.plan.exec.internal.AbstractLoadQueryDetails.generate(AbstractLoadQueryDetails.java:171) ~[AbstractLoadQueryDetails.class:4.3.1.Final]
    at org.hibernate.loader.plan.exec.internal.BasicCollectionLoadQueryDetails.<init>(BasicCollectionLoadQueryDetails.java:60) ~[BasicCollectionLoadQueryDetails.class:4.3.1.Final]
    at org.hibernate.loader.plan.exec.internal.BatchingLoadQueryDetailsFactory.makeCollectionLoadQueryDetails(BatchingLoadQueryDetailsFactory.java:101) ~[BatchingLoadQueryDetailsFactory.class:4.3.1.Final]
    at org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer.<init>(AbstractLoadPlanBasedCollectionInitializer.java:77) ~[AbstractLoadPlanBasedCollectionInitializer.class:4.3.1.Final]
    at org.hibernate.loader.collection.plan.CollectionLoader.<init>(CollectionLoader.java:112) ~[CollectionLoader.class:4.3.1.Final]
    at org.hibernate.loader.collection.plan.CollectionLoader$Builder.byKey(CollectionLoader.java:105) ~[CollectionLoader$Builder.class:4.3.1.Final]
    at org.hibernate.loader.collection.plan.AbstractBatchingCollectionInitializerBuilder.buildNonBatchingLoader(AbstractBatchingCollectionInitializerBuilder.java:45) ~[AbstractBatchingCollectionInitializerBuilder.class:4.3.1.Final]
    at org.hibernate.loader.collection.BatchingCollectionInitializerBuilder.createBatchingCollectionInitializer(BatchingCollectionInitializerBuilder.java:71) ~[BatchingCollectionInitializerBuilder.class:4.3.1.Final]
    at org.hibernate.persister.collection.BasicCollectionPersister.createCollectionInitializer(BasicCollectionPersister.java:343) ~[BasicCollectionPersister.class:4.3.1.Final]
    at org.hibernate.persister.collection.AbstractCollectionPersister.postInstantiate(AbstractCollectionPersister.java:676) ~[AbstractCollectionPersister.class:4.3.1.Final]
    at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:484) ~[SessionFactoryImpl.class:4.3.1.Final]
    at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1857) ~[Configuration.class:4.3.1.Final]
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:850) ~[EntityManagerFactoryBuilderImpl$4.class:4.3.1.Final]
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:843) ~[EntityManagerFactoryBuilderImpl$4.class:4.3.1.Final]
    at org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl.withTccl(ClassLoaderServiceImpl.java:399) ~[ClassLoaderServiceImpl.class:4.3.1.Final]
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:842) ~[EntityManagerFactoryBuilderImpl.class:4.3.1.Final]
    at org.hibernate.jpa.HibernatePersistenceProvider.createContainerEntityManagerFactory(HibernatePersistenceProvider.java:150) ~[HibernatePersistenceProvider.class:4.3.1.Final]
    at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:336) ~[LocalContainerEntityManagerFactoryBean.class:4.0.1.RELEASE]
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:318) ~[AbstractEntityManagerFactoryBean.class:4.0.1.RELEASE]
    ...

Что не так с моей картой? Как я могу предотвратить это исключение?

Интересно, что BasicCollectionPersister вроде инициализируется корректно. Отладка показывает, что, например. sqlUpdateRowString это update MyEntity_map set val1=?, val2=? where MyEntity_id=? and key1_id=? and key2=?.


person Christian Rudolph    schedule 22.02.2014    source источник
comment
Какую версию Hibernate вы используете?   -  person Peter Keller    schedule 22.02.2014
comment
Я использую спящий режим 4.3.1.Final.   -  person Christian Rudolph    schedule 22.02.2014


Ответы (1)


Из спецификации JPA 2.0:

Сопоставления отношений, определенные в классе встроенных идентификаторов, не поддерживаются.

См. также JPA — EmbeddedId с @ManytoOne.

ИЗМЕНИТЬ:

Hibernate 4.3.1.Final реализует JPA 2.1, поэтому мое первое предположение было неверным. Моя вина.

Проблема с отображением заключается в использовании @ElementCollection с встраиваемым CompositeKey, ссылающимся на сущность ReferencedEntity. Исключение в AbstractPropertyMapping возникает, когда Hibernate ищет ссылку от CompositeValue (sic!) до ReferencedEntity. Однако, поскольку такого отношения не существует, выдается исключение. Для меня неясно, является ли это ошибкой Hibernate или в соответствии со спецификациями JPA 2.1.

Я вижу следующие решения.

Решение 1

Измените CompositeKey на Entity, добавив поле id. В этом случае можно использовать аннотацию @OneToMany:

@Entity
public class CompositeKey {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @ManyToOne(targetEntity = ReferencedEntity.class)
    private ReferencedEntity key1;

    @Basic
    private Integer key2;

}

Решение 2

Добавьте дополнительный объект CompositeSampler, содержащий CompositeKey в качестве идентификатора и CompositeValue в качестве дополнительного атрибута. MyEntity будет содержать список CompositeSampler.

Решение 3

Добавьте ссылку с CompositeValue на ReferencedEntity.

Кстати, согласно спецификациям JPA 2.1: «Если встраиваемый класс используется в качестве ключа сопоставления, встраиваемый класс должен реализовывать методы hashCode и equals в соответствии со столбцами базы данных, с которыми сопоставляется встраиваемый».

person Peter Keller    schedule 22.02.2014
comment
Есть ли обходной путь, кроме объявления CompositeKey как @Entity? - person Christian Rudolph; 22.02.2014
comment
Я не уверен, действительно ли это проблема, потому что hibernate отображает @EmbeddedIds с элементами @ManyToOne, как и ожидалось, и правильно генерирует внешний ключ. И я даже не использую @EmbeddedId в коде, опубликованном выше. - person Christian Rudolph; 22.02.2014
comment
Спасибо за обновление. Решение 1 - это то, что я имел в виду под своим первым комментарием к вашему ответу. Я думаю, что это ошибка в спящем режиме, потому что мне удалось заставить спящий режим генерировать схему, когда я переименовал поле key1 в val1. Он находит имя свойства в CompositeValue и больше не жалуется. Но я не проверял, ведет ли себя объект ожидаемое (CRUD). Думаю написать отчет об ошибке. - person Christian Rudolph; 22.02.2014