数据传输对象 (dto):Hibernate 重复表映射

Posted

技术标签:

【中文标题】数据传输对象 (dto):Hibernate 重复表映射【英文标题】:Data Transfer Object (dto): Hibernate duplicate table mapping 【发布时间】:2015-05-06 08:29:44 【问题描述】:

我有一些大型 JPA (Hibernate) 实体,我想在一个大列表中显示它们。为了获得合理的性能并避免大量'joins',我想为我的实体创建一些 DTO。

实体有一个超类,所以我使用“表每类继承映射”

@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)

对于 DTO,我使用了相同的 JPA 映射注释,但排除了较大的字段。但这会导致异常:

org.hibernate.DuplicateMappingException: Duplicate table mapping

完整的堆栈跟踪:

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

是的,异常说明了我想要实现的目标:-)。

实体:

超类:

    @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;

子类1:

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

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

子类2:

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

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

超类DTO:

@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

子类2DTO:

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

我不厌其烦地制作了一个最小的github maven project 来说明这个问题,请随意尝试。您可以运行 OrmTest junit 测试来获取异常。

任何建议都将不胜感激。 谢谢

【问题讨论】:

添加完整的堆栈跟踪以便更好地理解。 很抱歉,它现在已包含在内。 这应该可以工作,只要你不让休眠创建你的表。但是有一个问题,您为什么将 DTO 创建为实体?为什么不将它们保留为简单的 POJO 并在查询中使用 constructor expressions,例如 select new mypackage.Subclass1DTO(sc1) from Subclass1 sc where ... 我正在使用 Spring Data JPA。所以我想为这些 DTO 提供一些 spring 数据存储库。所以他们应该是实体不是吗?而且不管怎样,我想尽量避免写纯sql。 您仍然可以使用 Spring Data 存储库,但您必须对所有方法使用 @Query,因为据我所知,它们不支持构造函数表达式。这不是 SQL,它是完全合法且记录在案的 JPQL,请查看我在之前评论中发布的链接。 【参考方案1】:

这可以通过使用 @ Embedded@ Embeddable 注释来实现。

例如:

@Entity
public class Employee 
    @Embedded
    private EmployeeDetails details;


@Embeddable
public class EmployeeDetails 

您还可以在超类中创建一个仅包含必填字段的嵌入类。 从而消除了创建 DTO 的开销。

如果您更喜欢使用 XML 映射,那么

<!-- Hibernate Mapping 1 -->

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

<!-- Hibernate Mapping 2 -->

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

【讨论】:

一旦我从数据库加载员工,嵌入的员工详细信息也默认加载?所以我最终不会得到同样的重物吗? 默认情况下,它们可用于 Employee 对象,但您可以使用 @Basic(fetch=FetchType.LAZY) 根据需要获取 Embedded 对象。 非常感谢,这是我实现我想做的最接近的目标。【参考方案2】:

您对两个不同的类使用相同的表名。例外情况也说明了这一点。

DTO-s 不是实体。删除那些注释。我假设,SuperClassDTO 也不是一个实体。 (你有一个名为SUPER_CLASS_DTO 的表吗?)

我认为您应该将@MappedSuperclass 用于基本实体,将@Entity 用于子类。然后,DTO-s 应该扩展超类,没有任何注释。

要了解区别,check this answer

编辑

超类:

@MappedSuperclass
public class SuperClass 

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

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

子类1:

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

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

子类2:

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

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

超类DTO:

删除

SubClass1DTO:

public class SubClass1Dto extends SuperClass 

子类2DTO:

public class SubClass2Dto extends SuperClass 

【讨论】:

Idd,它们不是真正的实体,没有表 super_class_dto。但是使用完整的实体会破坏制作 dto 的意义。我不想加载完整的实体对象,只加载我真正需要的字段。 是的,这就是为什么你应该扩展超类:public class SubClassDto extends SuperClass 。那么 DTO 将不包含 @Clob 例如 但是 subclassdto 仍然会映射到与子类相同的表? 你不需要SuperClassDto 我试过了,和以前一样的错误,因为subclassdto仍然指向与子类相同的表。所以也许我必须按照 Predrag Maric 的建议删除实体注释,但是我不能使用 spring data jpa 存储库..

以上是关于数据传输对象 (dto):Hibernate 重复表映射的主要内容,如果未能解决你的问题,请参考以下文章

数据传输对象和事务服务方法

如何使用 Springboot 和 Hibernate 在 DTO 和 Aggentity 类中映射 Postgres JSON 数据类型

在hibernate中用sql语句

Java中DAO/DTO/PO/VO/BO/QO/POJO

如何将休眠查询的结果映射到 DTO 对象?

踩过的坑 - 记录