Hibernate: как использовать пользовательскую коллекцию @Embeddables?

Я хочу использовать пользовательский тип коллекции для набора встраиваемых элементов. Как это сделать с помощью Hibernate 4.3.8 и Spring Boot с использованием конфигурации на основе Java?

Мой встраиваемый выглядит так

@Embeddable
public class MyEmbeddable implements Entry<Object, Integer> {
}

И сущность, у которой есть набор этого встраиваемого объекта, выглядит так

@Entity
public class MyOtherClass {
    @ElementCollection(fetch = FetchType.EAGER)
    private Set<MyEmbeddable> embeddables;
}   

и я хочу добиться чего-то вроде этого

@Entity
public class MyOtherClass {
    @ElementCollection(fetch = FetchType.EAGER)
    private MyCustomSet embeddables;
}   

с

public class MyCustomSet extends HashSet<MyEmbeddable> {

    //some custom methods...

}

Я пробовал с UserCollectionType и @CollectionType, но не удалось и не нашел любые учебники/примеры.

Мой пользовательский набор

public class MyCustomSet extends HashSet<MyEmbeddable> implements UserCollectionType {

    @Override
    public PersistentCollection instantiate(final SessionImplementor session, final CollectionPersister persister) throws HibernateException {
        return new PersistentSet();
    }

    @Override
    public PersistentCollection wrap(final SessionImplementor session, final Object collection) {
        return new PersistentSet(session, (Set) collection);
    }

    @Override
    public Iterator<Ingredient> getElementsIterator(final Object collection) {
        return ((Set<MyEmbeddable>) collection).iterator();
    }

    @Override
    public boolean contains(final Object collection, final Object entity) {
        return ((Set<MyEmbeddable>) collection).contains(entity);
    }

    @Override
    public Object indexOf(final Object collection, final Object entity) {
        return null;
    }

    @Override
    public Object replaceElements(final Object original, final Object target, final CollectionPersister persister, final Object owner, final Map copyCache,
            final SessionImplementor session) throws HibernateException {

        final Set originalSet = (Set) original;
        final Set targetSet = (Set) target;

        targetSet.clear();
        targetSet.addAll(originalSet);

        return targetSet;
    }

    @Override
    public Object instantiate(final int anticipatedSize) {
        return new MyCustomSet();
    }

}

Мой объект с пользовательским набором

@Entity
public class MyEntity {


    @ElementCollection(fetch = FetchType.EAGER)
    @CollectionType(type = "com.blubb.MyCustomSet")
    private MyCustomSet custom;

    public MyEntity() {
    }

    public MyEntity(final MyCustomSet custom) {
        this.custom = custom;
    }


    public MyCustomSet getCustom() {
        return this.custom;
    }

    public void setCustom(final MyCustomSet custom) {
        this.custom = custom;
    }

}

Выдает исключение

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.class]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1566)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:539)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:956)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:747)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:480)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:686)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:320)
    at org.springframework.boot.test.SpringApplicationContextLoader.loadContext(SpringApplicationContextLoader.java:100)
    at org.springframework.test.context.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:68)
    at org.springframework.test.context.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:86)
    at org.springframework.test.context.DefaultTestContext.getApplicationContext(DefaultTestContext.java:72)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:117)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:212)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:200)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:259)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:261)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:219)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:83)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:68)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:163)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.persistenceException(EntityManagerFactoryBuilderImpl.java:1239)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.access$600(EntityManagerFactoryBuilderImpl.java:120)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:855)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:845)
    at org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl.withTccl(ClassLoaderServiceImpl.java:398)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:844)
    at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:60)
    at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:343)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:318)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1625)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1562)
    ... 39 common frames omitted
Caused by: org.hibernate.AnnotationException: Illegal attempt to map a non collection as a @OneToMany, @ManyToMany or @CollectionOfElements: com.blubb.MyEntity.custom
    at org.hibernate.cfg.annotations.CollectionBinder.getCollectionBinder(CollectionBinder.java:330)
    at org.hibernate.cfg.AnnotationBinder.processElementAnnotations(AnnotationBinder.java:1922)
    at org.hibernate.cfg.AnnotationBinder.processIdPropertiesIfNotAlready(AnnotationBinder.java:963)
    at org.hibernate.cfg.AnnotationBinder.bindClass(AnnotationBinder.java:796)
    at org.hibernate.cfg.Configuration$MetadataSourceQueue.processAnnotatedClassesQueue(Configuration.java:3845)
    at org.hibernate.cfg.Configuration$MetadataSourceQueue.processMetadata(Configuration.java:3799)
    at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1412)
    at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1846)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:852)
    ... 47 common frames omitted

person krinklesaurus    schedule 26.04.2015    source источник


Ответы (1)


Вы не должны переопределять типы коллекций, связывая их как @ElementCollection. Hibernate использует собственные прокси-серверы для сбора данных, которые проверяют загрязненность объектов и предоставляют ленивые инициализация.

Даже если вы укажете HashSet<Entity>, Hibernate заменит тип на PersistentSet.

Тип UserCollectionType можно использовать для ассоциаций сущностей, но он может не работать для встраиваемых коллекций.

Если вы хотите преобразовать MyEmbeddable в объект, вот как будет выглядеть пользовательская коллекция:

@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "id")
@CollectionTypeInfo(name = "package.MyCustomSet")
public Set<MyEmbeddable> getMyEmbeddables() {
    ...
}
person Vlad Mihalcea    schedule 26.04.2015
comment
Спасибо. Я не знаю, как ваш ответ помогает мне. Мне нужен пользовательский тип коллекции, так как мне нужно переопределить и добавить некоторые методы в мою пользовательскую коллекцию. Использование коллекции Java, такой как HashSet, для меня не решение. Я предположил, что для этой цели существует UserCollectionType, поскольку у него есть некоторые методы для преобразования пользовательского типа коллекции в PersistentCollection, поэтому Hibernate может выполнять его проксирование/замену, но, к сожалению, это не работает. - person krinklesaurus; 27.04.2015
comment
Хорошо спасибо. Я ожидал, что это не сработает для Embeddables. Спасибо за объяснение. Я мог бы использовать описанный здесь подход к композиции (stackoverflow.com/a/3174394/1089459), затем :-( - person krinklesaurus; 27.04.2015