使用 EmbeddedID 创建新的 JPA 实体

Creating new JPA Entity with EmbeddedID

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

Foo -< FooBar >- Bar

当我 select 数据时它工作正常但是当我尝试创建关系的新实例时我可能会收到错误,因为 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
}

交叉点Table(和键)

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

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

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

@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 class 在引用的实体中。如果您在 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.