Создание новой сущности JPA с помощью EmbeddedId

#jpa #eclipselink

Вопрос:

У меня есть очень простой набор таблиц, моделирующих отношения «многие ко многим».

 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.

Если я добавлю

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

до persist того, как это сработает, но не следует @MapsId ли эффективно указывать JPA на отображение с помощью ключа?

Я предполагаю, что я должен устанавливать значения ключа @ManyToOne и, которые логически совпадают, поэтому у меня должно быть что-то неправильно настроено?

Я использую Eclipselink, если это имеет значение.

Комментарии:

1. Его значение равно нулю, потому что отношение равно нулю. Установите взаимосвязь, а не базовое сопоставление ключей при использовании MapsId — MapsId сообщает JPA, что связь управляет и устанавливает внешний ключ и базовое сопоставление, на которое он указывает, а не наоборот. Когда вы создаете новую транзакцию Foo, FooBar и bar в одной и той же транзакции, идентификаторы могут отсутствовать при использовании последовательности до более позднего времени, поэтому getId может быть равен нулю. MapsId и даже просто установление связи с аннотацией идентификатора позволяет JPA обрабатывать назначение производных идентификаторов, подобных этому, для вас.

2. Спасибо. Foo и Bar уже существующие объекты, так что никаких проблем с идентификаторами. Поскольку ключ полностью состоит из отношений, я могу полностью игнорировать ключ при создании нового объекта и просто задавать mapsId поля?

3. Кроме того, если бы мой составной ключ состоял как из отношения, так и из обычного столбца, то как бы они работали? Я предполагаю, что мне понадобится ключ, как указано выше, а также настройка отношений?

Ответ №1:

Когда вы используете MapsId, вы сообщаете JPA, что эта связь и значение первичного ключа целевой сущности используются для задания сопоставления, названного в значении MapsId. Затем JPA использует это сопоставление отношений для установки внешнего ключа И базового значения сопоставления при сбросе или фиксации. В вашем случае вы оставили связь НУЛЕВОЙ, что заставляет 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 заполнит ваши внешние ключи для вас, когда отношения не являются нулевыми, но для любых других полей требуется либо последовательность, либо ваши собственные механизмы, чтобы убедиться, что они заполнены до вставки, и все идентификаторы должны рассматриваться как неизменяемые в JPA.