1

I've some big JPA (Hibernate) entities that I would like to show in a big list. To have reasonable performance, and avoid a lot of 'joins', I'd like to create some DTO's for my entities.

The entities have a superclass, so I make use of "Table per class inheritance mapping":

@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)

For the DTO's I used the same JPA mapping annotations, with the larger fields excluded. But this results in an exception:

org.hibernate.DuplicateMappingException: Duplicate table mapping

Full stacktrace:

java.lang.IllegalStateException: Failed to load ApplicationContext
    at org.springframework.test.context.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:91)
    at org.springframework.test.context.DefaultTestContext.getApplicationContext(DefaultTestContext.java:74)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:116)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:82)
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:212)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:199)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:251)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:253)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:216)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:82)
    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:60)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:67)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:162)
    at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
    at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
    at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
    at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
    at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
    at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
    at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class org.nuytsm.HibernateDtoDuplicateTableMapping.springconfig.SpringOrmConfig: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: PU] Unable to build Hibernate SessionFactory
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1568)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:540)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:229)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
    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.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:125)
    at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:60)
    at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.delegateLoading(AbstractDelegatingSmartContextLoader.java:108)
    at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:260)
    at org.springframework.test.context.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:63)
    at org.springframework.test.context.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:83)
    ... 31 more
Caused by: javax.persistence.PersistenceException: [PersistenceUnit: PU] 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:341)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:318)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1627)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1564)
    ... 46 more
Caused by: org.hibernate.DuplicateMappingException: Duplicate table mapping SubClass1
    at org.hibernate.cfg.Configuration$MappingsImpl.addDenormalizedTable(Configuration.java:2965)
    at org.hibernate.cfg.annotations.TableBinder.buildAndFillTable(TableBinder.java:289)
    at org.hibernate.cfg.annotations.TableBinder.buildAndFillTable(TableBinder.java:339)
    at org.hibernate.cfg.annotations.EntityBinder.bindTable(EntityBinder.java:594)
    at org.hibernate.cfg.AnnotationBinder.bindClass(AnnotationBinder.java:677)
    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)
    ... 54 more

So yeah, the exception states kind of what I'd like to achieve :-).

The entities:

Superclass:

    @Entity

    @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
    public class SuperClass {

        @Id
        @GeneratedValue(strategy = GenerationType.TABLE)
        @Column(name = "SID", nullable = false)
        private Long id;

        @Column(name = "NAME")
        private String name;

Subclass1:

@Entity
@Table(name = "SubClass1")
public class SubClass1 extends SuperClass{

    @Column(name="LONGSTRING")
    @Lob
    private String tooLongStringSoINeedDTO;

Subclass2:

@Entity
@Table(name = "SubClass2")
public class SubClass2 extends SuperClass{

    @Column(name="BYTEARRAY")
    @Lob
    private byte[] someBigArray;

SuperclassDTO:

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class SuperClassDto {

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE)
    @Column(name = "SID", nullable = false)
    private Long id;

    @Column(name = "NAME")
    private String name;

SubClass1DTO:

@Entity
@Table(name = "SubClass1")
public class SubClass1Dto extends SuperClassDto{

SubClass2DTO:

@Entity
@Table(name = "SubClass2")
public class SubClass2Dto extends SuperClassDto{

I took the trouble to make a minimal github maven project to illustrate this problem, feel free to try it out. You can run the OrmTest junit test to get the exception.

Any suggestions would really be appreciated. Thanks

Mark
  • 590
  • 7
  • 17
  • Add complete stacktrace for better understanding. – OO7 May 06 '15 at 08:34
  • I'm sorry, it's included now. – Mark May 06 '15 at 08:38
  • 1
    This should work as long as you don't let hibernate create your tables. But one question, why did you create DTOs as entites? Why didn't you leave them as simple POJOs and use [constructor expressions](https://docs.oracle.com/html/E24396_01/ejb3_langref.html#ejb3_langref_constructor) in queries, like this `select new mypackage.Subclass1DTO(sc1) from Subclass1 sc where ...` – Predrag Maric May 06 '15 at 08:40
  • I'm using Spring Data JPA. So I'd like to have some spring data repositories for those DTO's. So they should be entities no? And anyway, I'd like to avoid writing pure sql as much as possible. – Mark May 06 '15 at 08:49
  • You could still use Spring Data repositories, but you would have to use `@Query` for all methods since they don't support constructor expressions as far as I know. And this is not SQL, it is perfectly legal and documented JPQL, check the link I posted in previous comment. – Predrag Maric May 06 '15 at 09:12

2 Answers2

2

You are using the same tablename for two different class. Thats what the exception also states.

DTO-s are not entities. Remove those annotations. And I assume, the SuperClassDTO also not an entity. (Do you have a TABLE named SUPER_CLASS_DTO?)

I think you should use @MappedSuperclass for the base entity, and @Entity for the subclasses. Then, the DTO-s should extend the superclass, without any annotation.

To understand the difference, check this answer

EDIT

Superclass:

@MappedSuperclass
public class SuperClass {

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE)
    @Column(name = "SID", nullable = false)
    private Long id;

    @Column(name = "NAME")
    private String name;

Subclass1:

@Entity
@Table(name = "SubClass1")
public class SubClass1 extends SuperClass {

    @Column(name="LONGSTRING")
    @Lob
    private String tooLongStringSoINeedDTO;

Subclass2:

@Entity
@Table(name = "SubClass2")
public class SubClass2 extends SuperClass {

    @Column(name="BYTEARRAY")
    @Lob
    private byte[] someBigArray;

SuperclassDTO:

delete it

SubClass1DTO:

public class SubClass1Dto extends SuperClass {

SubClass2DTO:

public class SubClass2Dto extends SuperClass {
Community
  • 1
  • 1
krstf
  • 627
  • 7
  • 25
  • Idd, they are not real entities, there's no table super_class_dto. But using the full entities would defeat the point of making a dto. I don't want to load the full entity object, just the fields that I really need. – Mark May 06 '15 at 08:42
  • yepp, thats why you shoul extend the superclass :`public class SubClassDto extends SuperClass {`. Then the DTO will not contain the `@Clob` for example – krstf May 06 '15 at 08:46
  • But the subclassdto would still map to the same table as the subclass? – Mark May 06 '15 at 08:54
  • you dont need the `SuperClassDto` – krstf May 06 '15 at 08:56
  • I tried it, same error as before, because the subclassdto still points to the same table as the subclass. So maybe I'd have to drop the entity annotations as Predrag Maric suggested, but then I can't use spring data jpa repositories.. – Mark May 06 '15 at 09:00
  • edited my answer. yes, you should drop the annotations on the dtos, thats what i wrote. spring data is another question. – krstf May 06 '15 at 09:02
2

This can be achieved by using @ Embedded & @ Embeddable annotations.

For Example:

@Entity
public class Employee {
    @Embedded
    private EmployeeDetails details;
}

@Embeddable
public class EmployeeDetails {
}

You can also create an embedded class in superclass which contains the required field only. Thus removing overhead of creating DTOs.

OR

If you prefer to use XML Mapping then

<!-- Hibernate Mapping 1 -->

<class name="com.SubClass1" table="SubClass1_Table">

<!-- Hibernate Mapping 2 -->

<class name="com.SubClass1" table="SubClass1_Table">
OO7
  • 2,785
  • 1
  • 21
  • 33
  • Once I load the employee from the db, the embedded employeedetails are alse loaded by default? So don't I end up with the same heavy object? – Mark May 06 '15 at 09:09
  • By default they are available with Employee object but, you can fetch the Embedded object as they required by using [@Basic(fetch=FetchType.LAZY)](http://docs.oracle.com/javaee/7/api/javax/persistence/Basic.html). – OO7 May 06 '15 at 09:18
  • Thanks alot, that's the closest I'll get to achieve what I want to do. – Mark May 06 '15 at 09:22