【问题标题】:JPA 2 - How to build entity that has Primary key that is also a foreign key using Spring Data JPA?JPA 2 - 如何使用 Spring Data JPA 构建具有主键也是外键的实体?
【发布时间】:2017-11-30 20:28:27
【问题描述】:

给定以下表格:

Car
int id PK
int modelId FK

CarDetails
int carId PK, FK to Car.id
varchar(50) description

我如何表明CarDetails@Id 也是Car 的外键?

我试过了:

@Entity
public class Car {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @ManyToOne
    @JoinColumn(name = "modelId", nullable = false)
    private Model model;

    //setters & getters
}

@Entity
public class CarDetails {

    @Id
    @OneToOne
    @JoinColumn(name = "carId", nullable = false)
    private Car car;

    private String description;

    //setters & getters
}

但是,我得到了错误

org.hibernate.MappingException: Composite-id class must implement Serializable: com.example.CarDetails

实施Serializable 后,我得到This class [class com.example.CarDetails] does not define an IdClass。但是在将@IdClass(Car.class) 添加到CarDetails 类后,我仍然会收到错误消息。

更新

IdClass 错误源自 Spring: Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'carDetailsRepository': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: This class [class com.example.CarDetails] does not define an IdClass

这是 CarDetailsRepository:

public interface CarDetailsRepository extends JpaRepository<CarDetails, Car> {

}

以下是我的 gradle 构建文件的相关部分:

plugins {
    id 'java'
    id 'eclipse'
    id 'maven-publish'
    id 'io.spring.dependency-management' version '1.0.3.RELEASE'
}

repositories {
    mavenCentral()
    mavenLocal()
}


dependencies {

    pmd group: 'org.hibernate', name: 'hibernate-tools', version: '5.2.3.Final'
    pmd group: 'org.hibernate', name: 'hibernate-core', version: '5.2.10.Final'
    pmd group: 'org.hibernate.common', name: 'hibernate-commons-annotations', version: '5.0.1.Final'

    compile('org.springframework.boot:spring-boot-starter')
    compile('org.springframework.boot:spring-boot-starter-data-jpa')
    compile('org.hibernate:hibernate-validator:5.2.4.Final')
    compile('org.hibernate:hibernate-envers:5.2.10.Final')
    compile('org.hibernate:hibernate-core:5.2.10.Final')
    compile('org.hibernate.common:hibernate-commons-annotations:5.0.1.Final')
    runtime('net.sourceforge.jtds:jtds:1.3.1')
    runtime('com.microsoft.sqlserver:sqljdbc:4.2')
    runtime('javax.el:javax.el-api:2.2.4')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

dependencyManagement {
    imports { mavenBom('org.springframework.boot:spring-boot-dependencies:1.5.4.RELEASE') }
}

【问题讨论】:

  • 您的映射似乎没问题。你确定你使用的是 JPA 2?
  • 实现可序列化?
  • @crizzis - 我使用的是休眠 5,我认为它是 JPA 2 的实现。
  • @Rossi - 不确定我是否理解这个问题。我收到一条错误消息,指出如果我不这样做,我必须实现 Serializable

标签: java hibernate jpa spring-data-jpa jpa-2.0


【解决方案1】:

您可以尝试像这样映射CarDetails

@Entity
public class CarDetails {

    @Id
    private int id;

    @MapsId
    @OneToOne
    @JoinColumn(name = "carId", nullable = false)
    private Car car;

    private String description;

    //setters & getters
}

注意@MapsId 注释。

【讨论】:

  • @MapsId 适用于嵌入而不是实体。
  • @AlvinThompson 这应该是有效的,根据 JPA 2.1 规范,第 2.4.1.3 节,示例 4,案例(b):“依赖实体具有与关系属性对应的单个主键属性.主键属性与父实体的主键基本类型相同。关系属性应用的MapsId注解表示主键是由关系属性映射的。"
  • 如果你编辑你的答案并给出一个快速工作的例子,我会删除反对票(我讨厌反对票)。
【解决方案2】:

可能最好的方法是在您的CarDetails 实体中引用carId 两次——一次用于@Id,一次用于外键引用。您必须使用 insertable=falseupdatable=false 声明其中一个引用,这样 JPA 就不会在尝试在两个位置管理同一列时感到困惑:

@Entity
public class CarDetails {

    @Id
    @Column(name = "carId", insertable = false, updatable = false)
    private int carId; // don't bother with getter/setter since the `car` reference handles everything

    @OneToOne
    @JoinColumn(name = "carId", nullable = false)
    private Car car;

    private String description;

    //setters & getters
}

它看起来很奇怪,但它会起作用,而且它实际上是(最)首选的方法。

【讨论】:

  • 我应该提到,这是首选方法的原因是因为如果您的第二个实体的 ID 是由两个(或更多) 不同的实体,并且您需要对这些实体的外键引用。因此,为了保持一致,请始终使用此表单。
  • 谢谢。这在我的示例中很有效,其中carIdint。但是,如果将carId 更改为String,我将不得不在保存实体之前手动设置一个非空值。否则会抛出异常。不必用 int 设置它,因为它有一个默认值(即 0)。
  • 如果carId 是一个字符串,那么car.id 也必须是一个字符串。它仍然有效。有信心! :)
  • 实际上,在那种情况下你是对的——你需要在两个地方都设置它。我忘了添加,在CarDetails.setCar(Car newCar) 的定义中,您可能需要选择添加carId = newCar.getId();(当然要进行空检查)以避免手动执行此操作。
  • @James 那行得通,但我认为您并没有降低复杂性;您只是将它从 JPA 移动到数据库(因为您必须添加一列)。更糟糕的是,插入会慢得多(现在有 2 个索引,数据库必须为每个插入生成一个新的自动增量 ID)。
猜你喜欢
  • 2017-12-11
  • 2018-12-27
  • 1970-01-01
  • 1970-01-01
  • 2015-02-03
  • 2021-12-21
  • 1970-01-01
  • 2023-03-08
  • 2022-11-29
相关资源
最近更新 更多