【问题标题】:Creating new JPA Entity with EmbeddedID使用 EmbeddedID 创建新的 JPA 实体
【发布时间】:2021-07-28 16:13:34
【问题描述】:

我有一组非常简单的表格来模拟多对多关系。

Foo -< FooBar >- Bar

当我选择数据时工作正常,但当我尝试创建关系的新实例时,我可能会收到错误,因为 JPA 正在尝试插入空值。

我必须同时设置 @ManyToOne 值和键(代表相同的东西),这让我觉得设置不正确。

因此,问题是如何正确设置注释以创建新的FooBar 关系?

实体

@Entity
@Table(name = "FOO")
public class Foo implements Serializable {

  @Id
  @Column(name = "FOO_ID")
  private int id;
  
  @column(name = "FOO_DESC")
  private String desc;
  
  @OneToMany(mappedBy = "foo")
  private List<FooBar> fooBars;

  //getters / setters / hashCode / equals
}
@Entity
@Table(name = "BAR")
public class Bar implements Serializable {

  @Id
  @Column(name = "BAR_ID")
  private int id;
  
  @column(name = "BAR_DESC")
  private String desc;
  
  @OneToMany(mappedBy = "bar")
  private List<FooBar> fooBars;

  //getters / setters / hashCode / equals
}

交集表(和键)

@Embeddable
public class FooBarKey implements Serializable {
  private int fooId;
  private int BarId;

  //getters / setters / hashCode / equals
}
@Entity
@Table(name ="FOO_BAR_XREF")
public class FooBar implements Serializable {

  @EmbeddedId
  private FooBarKey key;
  
  @MapsId("fooId")
  @ManyToOne
  @JoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID")
  private Foo foo;

  @MapsId("barId")
  @ManyToOne
  @JoinColumn(name = "BAR_ID", referencedColumnName = "BAR_ID")
  private Bar bar;  
  
  @Column(name = "DESC")
  private String desc; 
  
  //getters / setters / hashCode / equals
}

创建关系

最后我正在创建一个新的交集实例:

//foo and bar exist and are populated
FooBar fb = new FooBar();
FooBarKey fbk = new FooBarKey();
fbk.setFooId(foo.getId());
fbk.setBarId(bar.getId());
fb.setKey(fbk);
fb.setDesc("Some Random Text");

entityManager.persist(fb);

此时 JPA 错误提示它无法将 null 插入 FOO_ID。

我已经检查并在我持久化对象时,键填充了 Foo 和 Bar ID。

如果我添加

fb.setFoo(foo);
fb.setBar(bar);

persist 之前它可以工作,但@MapsId 是否应该有效地告诉JPA 使用密钥进行映射?

我认为我应该同时设置 @ManyToOne 和键值,它们在逻辑上是相同的,所以我必须有一些配置不正确?

如果有什么不同,我正在使用 Eclipselink。

【问题讨论】:

  • 它为空,因为关系为空。设置关系,而不是使用 MapsId 时的基本键映射 - MapsId 告诉 JPA 关系控制并将设置外键及其指向的基本映射,而不是相反。当您在同一事务中创建新的 Foo、FooBar 和 bar 时,如果稍后使用排序,ID 可能不存在,因此 getId 可能为空。 MapsId 甚至只是与 Id 注释建立关系让 JPA 为您处理分配这样的派生 Id。
  • 谢谢。 FooBar 已经是现有对象,因此 ID 没有问题。由于密钥完全由关系组成,我可以在创建新对象时完全忽略密钥并设置 mapsId 字段?
  • 另外,如果我的复合键同时包含关系和普通列,那么它们将如何工作?我想我需要上面的键以及设置关系?

标签: jpa eclipselink


【解决方案1】:

当您使用 mapsId 时,您是在告诉 JPA,此关系以及目标实体的主键值用于设置 mapsId 值中命名的映射。然后,JPA 在刷新或提交时使用此关系映射来设置外键和基本映射值。在您的情况下,您将关系保留为 NULL,这会强制 FK 在插入时为空。

这允许延迟排序,因为在创建和遍历图表时,您可能没有在引用实体中生成主键 - JPA 将计算和填充这些值,并在需要时通过图表传播它们。

如果您没有在模型中使用 ID 类,最简单的解决方案就是删除它并避免开销:

@Entity
@IdClass(package.FooBarKey.class)
@Table(name ="FOO_BAR_XREF")
public class FooBar implements Serializable {
  
  @Id
  @ManyToOne
  @JoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID")
  private Foo foo;

  @Id
  @ManyToOne
  @JoinColumn(name = "BAR_ID", referencedColumnName = "BAR_ID")
  private Bar bar;  
  
  @Column(name = "DESC")
  private String desc; 
  
  //getters / setters / hashCode / equals
}


public class FooBarKey implements Serializable {
  private int foo;
  private int bar;
}

IdClass 与 @EmbeddedId 所需的限制类似,但还有一个限制 - 其中的名称必须与 @Id 指定的属性名称匹配,但类型必须与引用实体中的 ID 类相同。如果您在 Foo 和 Bar 中使用基本映射,这很容易,但可能会更复杂。

向复合键添加更多内容很容易:

@Entity
@IdClass(package.FooBarKey.class)
@Table(name ="FOO_BAR_XREF")
public class FooBar implements Serializable {
  
  @Id
  @ManyToOne
  @JoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID")
  private Foo foo;

  @Id
  @ManyToOne
  @JoinColumn(name = "BAR_ID", referencedColumnName = "BAR_ID")
  private Bar bar;

  @Id
  private int someValue
  
  @Column(name = "DESC")
  private String desc; 
  
  //getters / setters / hashCode / equals
}


public class FooBarKey implements Serializable {
  private int foo;
  private int bar;
  private int someValue
}

当关系不为空时,JPA 将为您填充外键,但任何其他字段都需要排序或您自己的机制以确保在插入之前填充它们,并且所有 Id 都应在 JPA 中被视为不可变。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-12-14
    • 2011-01-21
    • 2017-04-30
    • 2013-09-23
    • 1970-01-01
    • 1970-01-01
    • 2022-01-17
    • 1970-01-01
    相关资源
    最近更新 更多