使用一对多关系保存实体

4
我认为我的Hibernate数据库设置有问题。我有一些实体类 Citizen,这些实体类与 WeeklyCare 存在一对多的关系。以下是相关代码。
Citizen:
@Entity
@Table(name = "citizens")
public class Citizen {
@Id
@Size(max = 10, min = 10, message = "CPR must be exactly 10 characters")
private String cpr;

@OneToMany()
@JoinColumn(name = "cpr")
private List<WeeklyCare> weeklyCare;
}

每周关注:

@Entity
public class WeeklyCare {
@EmbeddedId
private WeeklyCareIdentifier weeklyCareIdentifier;
}

每周照护标识:

@Embeddable
public class WeeklyCareIdentifier implements Serializable {

@NotNull
@Size(max = 10, min = 10, message = "CPR must tbe exactly 10 characters")
private String cpr;

@NotNull
private Integer week;

@NotNull
private Integer year;
}

在将数据保存到数据库时,我遇到了一些问题:

  1. 由于需要一个 Citizen,因此我无法首先保存 WeeklyCare
  2. 当我将市民发送到后端时,对象包含 WeeklyCare 的列表。当我尝试保存市民时,会出现以下错误:无法找到 id 为 Application.Models.WeeklyCareIdentifier@b23ef67b 的 Application.Models.WeeklyCare

在保存之前,我可以通过清除 CitizenWeeklyCare 列表,然后在保存之后保存 WeeklyCare 列表来解决此问题,但这感觉像是一种可怕的方法。

我想让 hibernate 在保存市民时忽略 WeeklyCare 列表,但在获取市民时认可它。这是可能的吗?或者有更好的方法吗?谢谢。

1个回答

0
  1. 我无法先保存WeeklyCare,因为它需要一个Citizen。

您在两个实体中使用了“cpr”标识符:

  • 它是Citizen的主要Id
  • 它是WeeklyCare的复合Id的一部分

理论上,您可以创建一个WeeklyCare列表(不过现在的建模方式不行),稍后更新每个WeeklyCare与Citizen的关联。

  1. 当我将公民发送到后端时,对象包含WeeklyCare列表。当我尝试保存公民时,它给出以下错误:无法找到id为Application.Models.WeeklyCareIdentifier@b23ef67b的Application.Models.WeeklyCare

映射一对多关联的最佳方法是双向的。这也可以避免在仅使用@ JoinColumn的@OneToMany时Hibernate生成一些不必要的查询。

1)从WeeklyCareIdentifier类中删除cpr(并可能重命名该类)。

@Embeddable
public class WeeklyCareIdentifier implements Serializable {

  @NotNull
  private Integer week;

  @NotNull
  private Integer year;

  //constructors, getters, setters
}

2) 删除复合 @EmbeddedId,改为使用 Long id 字段:

@Entity
public class WeeklyCare {

  @Id
  @GeneratedValue
  private Long id;

  @Embedded
  private WeeklyCareIdentifier weeklyCareIdentifier;

  //constructors, getters, setters
}

3) 转向双向映射:

@Entity
@Table(name = "citizens")
public class Citizen {
  @Id
  @Size(max = 10, min = 10, message = "CPR must be exactly 10 characters")
  private String cpr;

  @OneToMany(
      mappedBy = "citizen",
      cascade = CascadeType.ALL, //cascade all operations to children
      orphanRemoval = true //remove orphaned WeeklyCare if they don't have associated Citizen
  )
  private List<WeeklyCare> weeklyCares = new ArrayList<>(); //init collections to avoid nulls

    //constructors, getters, setters

    //add utility methods to manipulate the relationship

    public void addWeeklyCare(WeeklyCare weeklyCare) {
      weeklyCares.add(weeklyCare);
      weeklyCare.setCitizen(this);
    }

    public void removeWeeklyCare(WeeklyCare weeklyCare) {
      weeklyCares.remove(weeklyCare);
      weeklyCare.setCitizen(null);
    }
}

并且:

@Entity
public class WeeklyCare {

    @Id
    @GeneratedValue
    private Long id;

    //having reference to the citizen entity from WeeklyCare
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "citizen_cpr")
    private Citizen citizen;

    @Embedded
    private WeeklyCareIdentifier weeklyCareIdentifier;

    //constructors, getters, setters
}

我也建议为实体使用长ID,即使CPR是唯一的等等。将CPR转换为普通列,并引入一个由数据库生成的ID列,您可以在内部域中使用它进行连接,并将CPR视为纯用户面向数据列。

谢谢!用这种方式做,从数据库创建的所有WeeklyCare对象都会包含一个Citizen吗?我不是真的需要它们,所以这就是阻止我使用双向映射的原因。 - Jesper
该关联在定义时是惰性的,WeeklyCare仅将公民的ID保存在其中。当您从数据库加载WeeklyCare实体时,它不会加载与其关联的公民。只有在对WeeklyCare执行getCitizen()时才会执行SELECT。请记住,JPA / Hibernate查询是对JPA提供程序持久性上下文中的实体进行查询。Hibernate的工作是确定它是否应该从L1缓存、启用L2缓存还是实际查询数据库。 - hovanessyan
嘿,我现在已经尝试过了。当我调用citizenRepository.findAll();将其返回到我的前端时,我会收到一个stackoverflow错误。这是因为JSON响应返回每个Citizen及其WeeklyCare列表。每个WeeklyCare包含一个再次包含WeeklyCare列表的CItizen,如此循环。我该如何解决这个问题? - Jesper
这不是由映射引起的 - 这可能是您代码中的其他原因。您可以查看大师自己在本文中的另一个示例:https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/ - hovanessyan
经过一些研究,我认为这是预期的行为,并且是由双向映射引起的。目前,我通过在WeeklyCare中的Citizen上使用@JsonIgnore注释来解决它。不确定这是否是最佳解决方案。还有一个名为@JsonIdentityInfo的注释可能会有用。 - Jesper
双向映射可能会导致某些框架无法正常工作(例如Lombock,Jackson)。首先,我认为实体 - 即持久性层内部不应直接用于Jackson操作。最好将实体转换为DTO对象,并将DTO对象传递到调用堆栈中,直到它们到达REST层 - 并在那里对它们进行序列化(DTO对象不包含双向映射所需的字段)。如果必须在实体上使用Jackson,则可以使用JsonManagedReference和JsonBackReference注释帮助Jackson理解关系。 - hovanessyan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接