【问题标题】:Mapping entities with composite foreign keys in JPA在 JPA 中使用复合外键映射实体
【发布时间】:2017-09-04 21:05:57
【问题描述】:

我有一个应用程序,它的所有数据库表都有一个名为 BRAND 的列,该列位于 PRIMARY KEY

在客户表中,我有一列id (autoincremnt),它与品牌一起构成一个复合键,映射到 Java 实体如下:

客户实体

@Entity
@Table(name = "CUSTOMERS")
public class CustomerEntity implements Serializable {

    @EmbeddedId
    private CustomerId id;

    //getters and setters and other attributes hidden

}

客户的复合键

@Embeddable
public class CustomerId implements Serializable {

    @Column(name = "BRAND")
    private String brand;
    @Column(name = "ID_CUSTOMER")
    private String idCustomer;

    //getters and setters hidden
}

问题在于将客户实体映射为订单实体的外键。

与客户的订单实体

@Entity
@Table(name = "ORDERS")
public class OrderEntity implements Serializable {

    @EmbeddedId
    private OrderId id;

    @Column(name = "STATUS")
    private Integer status;

    @Column(name = "DELIVERY_DATE")
    private LocalDateTime deliveryDate;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumns(value = {
        @JoinColumn(name = "ID_CUSTOMER", referencedColumnName = "ID_CUSTOMER", updatable = false, insertable = false),
    @JoinColumn(name = "BRAND", referencedColumnName = "BRAND", updatable = false, insertable = false)})
    private CustomerEntity customer;

}

订单编号

@Embeddable
public class OrderId implements Serializable {

    @Column(name = "ID_ORDER")
    private String idOrder;
    @Column(name = "BRAND")
    private String brand;

}

这样,我可以阅读订单并通过致电orderRepository.findAll() 将客户聚集在一起。

但是,当我保存新订单时,将现有客户传递给订单实体,通过调用orderRepository.save(order),客户并未保存在订单中。

如果我进行如下简单映射:

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumns(value = {
    @JoinColumn(name = "ID_CUSTOMER", referencedColumnName = "ID_CUSTOMER"),
@JoinColumn(name = "BRAND", referencedColumnName = "BRAND")})
private CustomerEntity customer;

我得到以下异常:

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:1628)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1081)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:856)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:542)
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:737)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:370)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:314)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1162)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1151)
    at br.com.dasa.DasaIntegracaoUraApplication.main(DasaIntegracaoUraApplication.java:33)
    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:498)
    at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)
Caused by: javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.persistenceException(EntityManagerFactoryBuilderImpl.java:954)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:882)
    at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:60)
    at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:353)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:370)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:359)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1687)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1624)
    ... 21 common frames omitted
Caused by: org.hibernate.MappingException: Repeated column in mapping for entity: br.com.dev.orm.entity.OrderEntity column: brand (should be mapped with insert="false" update="false")
    at org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:830)
    at org.hibernate.mapping.PersistentClass.checkPropertyColumnDuplication(PersistentClass.java:848)
    at org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:870)
    at org.hibernate.mapping.PersistentClass.validate(PersistentClass.java:605)
    at org.hibernate.mapping.RootClass.validate(RootClass.java:265)
    at org.hibernate.boot.internal.MetadataImpl.validate(MetadataImpl.java:329)
    at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:443)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:879)
    ... 27 common frames omitted

如果我在客户中添加可插入和可更新的属性,如下:

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumns(value = {
    @JoinColumn(
        name = "ID_CUSTOMER", 
        referencedColumnName = "ID_CUSTOMER"
    ),
    @JoinColumn(
        name = "BRAND", 
        referencedColumnName = "BRAND", 
        insertable = false, 
        updatable = false
    )
})
private CustomerEntity customer

我得到以下异常:

java.lang.IllegalStateException: Failed to load ApplicationContext
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
    at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:83)
    at org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener.prepareTestInstance(SpringBootDependencyInjectionTestExecutionListener.java:47)
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:287)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:289)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:247)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
    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:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
    at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:283)
    at org.apache.maven.surefire.junit4.JUnit4Provider.executeWithRerun(JUnit4Provider.java:173)
    at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:153)
    at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:128)
    at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:203)
    at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:155)
    at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:103)
Caused by: 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 org.hibernate.AnnotationException: Mixing insertable and non insertable columns in a property is not allowed: br.com.dasa.orm.entity.RelacaoUraExameEspecialidadeEntity.especialidade
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1628)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1081)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:856)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:542)
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:737)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:370)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:314)
    at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:120)
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:98)
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:116)
    ... 25 common frames omitted
Caused by: org.hibernate.AnnotationException: Mixing insertable and non insertable columns in a property is not allowed: br.com.dev.orm.entity.OrderEntity.customer
    at org.hibernate.cfg.Ejb3Column.checkPropertyConsistency(Ejb3Column.java:710)
    at org.hibernate.cfg.AnnotationBinder.bindManyToOne(AnnotationBinder.java:2937)
    at org.hibernate.cfg.AnnotationBinder.processElementAnnotations(AnnotationBinder.java:1763)
    at org.hibernate.cfg.AnnotationBinder.processIdPropertiesIfNotAlready(AnnotationBinder.java:911)
    at org.hibernate.cfg.AnnotationBinder.bindClass(AnnotationBinder.java:738)
    at org.hibernate.boot.model.source.internal.annotations.AnnotationMetadataSourceProcessorImpl.processEntityHierarchies(AnnotationMetadataSourceProcessorImpl.java:245)
    at org.hibernate.boot.model.process.spi.MetadataBuildingProcess$1.processEntityHierarchies(MetadataBuildingProcess.java:222)
    at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:265)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:847)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:874)
    at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:60)
    at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:353)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:370)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:359)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1687)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1624)
    ... 41 common frames omitted

订单表结构

BRAND    |  ID_ORDER   |    ID_CUSTOMER   |   STATUS   |   DELIVERY_DATE

在 Order 类中映射 Customer(复合外键)的正确方法是什么?

【问题讨论】:

  • the customer is not saved in the order 是什么意思,你的意思是 FK 列是空的?您是如何尝试保存的?
  • 是的,ID_CUSTOMER 列为空。我用 JpaRepository 保存:orderRepository.save(order)。
  • 这通常发生在您没有设置对实体的反向引用时。假设以下内容:Parent p = new Parent(); Child c = new Child(); p.setChild(c) 错误,因为您缺少 c.setParent(p)

标签: java hibernate jpa orm hibernate-mapping


【解决方案1】:

我发现多个问题:

  1. 为什么idCustomer 属性是String。如果是AUTO_INCREMENTED 列,不应该是LongInteger 吗?

  2. 您没有显示将客户信息保存到订单中的代码,但问题是您使用了 updatable = false, insertable = false,只有在复制映射时才应该使用它,例如当客户 ID 包含在订单中时PK。只需将映射更改为:

     @ManyToOne(fetch = FetchType.LAZY)
     @JoinColumns(value = {
         @JoinColumn(
             name = "ID_CUSTOMER", 
             referencedColumnName = "ID_CUSTOMER"
         ),
         @JoinColumn(
             name = "BRAND", 
             referencedColumnName = "BRAND", 
             insertable = false, 
             updatable = false
         )
     })
     private CustomerEntity customer;
    

, 一切都应该正常工作。

  1. 并从 customer 关联中删除 EAGER 提取,因为它可能是性能问题的主要原因并导致 N+1 查询问题。

【讨论】:

  • 感谢您的关注。我已经阅读了您的文章,但它没有解释我的问题,该列由主键和外键共享。我错了,它不是 AUTO_INCREMENTED 列;如您在编辑后的问题中所见,此解决方案不起作用。
  • 没有看到 OrderId 映射就无法判断。
  • OrderId 类已添加。
  • 不是。不允许在属性中混合可插入和不可插入的列。答案已更新。
【解决方案2】:

我遇到了类似的问题并使用了以下解决方案。 正如您已经指出的,不允许在属性中混合可插入和不可插入的列。因此,我将两个 @JoinColumn 注释都保留为不可插入,并包含一个仅映射您要保存的列的附加属性。例如:

@Entity
@Table(name = "ORDERS")
public class OrderEntity implements Serializable {

    @EmbeddedId
    private OrderId id;

    // ...

    @ManyToOne
    @JoinColumns(value = {
        @JoinColumn(name = "ID_CUSTOMER", referencedColumnName = "ID_CUSTOMER", updatable = false, insertable = false),
        @JoinColumn(name = "BRAND", referencedColumnName = "BRAND", updatable = false, insertable = false)})
    private CustomerEntity customer;

    // This additional property only exists to allow writing to ID_CUSTOMER
    @Column(name = "ID_CUSTOMER")
    private String idCustomer;

    // The customer setter also sets the idCustomer, for consistency
    public void setCustumer(CustomerEntity customer) {
        this.customer = customer;
        this.idCustomer = customer != null ? customer.getId().getIdCustumer() : null;
    }

}

这感觉像是一个 hack,但这是我发现的唯一可行的解​​决方案。

另外,请注意我在setCustumer 中强制customeridCustomer 之间的一致性。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-02-21
    • 2019-04-22
    • 2017-01-30
    • 2011-03-20
    • 1970-01-01
    • 2021-09-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多