Отображение отношения @OneToMany с первичным ключом в качестве внешнего ключа - PullRequest
0 голосов
/ 05 мая 2019

Я пытаюсь отобразить отношение таблицы «один ко многим», описанное на следующей диаграмме в JPA:

Activity Diagram JPA

Как видно, в таблице "activity_property" используется составной первичный ключ, в котором (id,name) и столбец "id" являются внешним ключом для столбца "id" в таблице "activity".

Мои текущие объекты отображаются таким образом (некоторые вспомогательные методы для ясности опущены):

Activity.java

@Entity
@Getter
@Setter
public class Activity {
   @Id
   @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "act_id_generator")
   @SequenceGenerator(name = "act_id_generator", sequenceName = "activity_id_seq", allocationSize = 1, initialValue = 1)
   private Integer id;

   private String name;

   @OneToMany(mappedBy = "id.activityId", cascade = CascadeType.ALL, orphanRemoval = true)
   private List<ActivityProperty> properties;
}

ActivityProperty.java

@Entity
@Getter
@Setter
@Table(name = "activity_property")
public class ActivityProperty {
    @EmbeddedId
    private ActivityPropertyId id;

    private String value;

    @Enumerated(EnumType.STRING)
    private PropertyType type;
}

ActivityPropertyId.java

@Embeddable
@Getter
@Setter
@ToString
public class ActivityPropertyId implements Serializable {

    @Column(name = "id")
    private Integer activityId;

    private String name;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ActivityPropertyId that = (ActivityPropertyId) o;
        return activityId.equals(that.activityId) &&
                name.equals(that.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(activityId, name);
    }
}

Когда я пытаюсь сохранить активность таким образом:

Activity activity = createActivity("Activity_1");
activity.addProperty(ActivityProperty.from("Prop1", "value1", PropertyType.PARAMETER));
activityDAO.persist(activity);

Я вижу следующие следы в Hibernate:

Hibernate: call next value for activity_id_seq
Hibernate: insert into activity (name, id) values (?, ?)
Hibernate: insert into activity_property (type, value, id, name) values (?, ?, ?, ?)
05-05-2019 12:26:29.623 [main] WARN  o.h.e.jdbc.spi.SqlExceptionHelper.logExceptions - SQL Error: 23502, SQLState: 23502
05-05-2019 12:26:29.624 [main] ERROR o.h.e.jdbc.spi.SqlExceptionHelper.logExceptions - NULL not allowed for column "ID"; SQL statement:
insert into activity_property (type, value, id, name) values (?, ?, ?, ?) [23502-199]

Кажется, автоматически сгенерированный идентификатор для Activity не использовался во второй вставке для ActivityProperty.

Я не могу понять, как правильно сопоставить такого рода отношения. Чего-то не хватает в моих аннотациях JPA?

Ответы [ 2 ]

1 голос
/ 06 мая 2019

Насколько я могу судить, JPA не поддерживает это с помощью cascade операций.Вы слишком много просите от JPA.У меня нет конкретной ссылки, которая говорит об этом, но вы можете подойти как можно ближе к приведенному ниже коду.Он не будет работать, потому что Hibernate (по крайней мере) не знает, как автоматически сопоставить activity.id с PK класса ActivityProperty.

@Entity
@Getter
@Setter
@Table(name = "activity_property")
@IdClass(ActivityPropertyId.class)
public class ActivityProperty {
    @ManyToOne
    @Id
    @JoinColumn(name="id", referencedColumnName="id")
    private Activity activity;

    @Id
    private String name;
    private String value;

}

@Getter
@Setter
@ToString
public class ActivityPropertyId implements Serializable {
    public ActivityPropertyId() {}

    @Column(name = "id")
    private Integer activity;

    private String name;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ActivityPropertyId that = (ActivityPropertyId) o;
        return activity.equals(that.activity) &&
                name.equals(that.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(activity, name);
    }
}

Это даст исключение

Причина: java.lang.IllegalArgumentException: Невозможно установить модель поля java.lang.Integer.ActivityPropertyId.activity для model.Activity

И у меня естьне видел ничего хорошего в этом.ИМХО cascade - рай для дурака, и вы должны сами сохранять экземпляры ActivityProperty и использовать двунаправленное отображение только для запроса.Это действительно так, как это должно быть использовано.Если вы выполните несколько простых операций сохранения в списке OneToMany через каскад и посмотрите на сгенерированный SQL, вы увидите ужасные вещи.

1 голос
/ 05 мая 2019

Атрибут mappedBy используется, чтобы указать, что это отношение не используется для сохранения, и для этой цели используется атрибут в mappedBy.

Поэтому создайте обратную ссылку от ActivityProperty доActivity и используйте его как сопоставленный, или вы должны использовать @JoinColumn для объявления внешнего ключа.

Это будет выглядеть так.Атрибут name указывает на имя внешнего ключа в целевой таблице.

@JoinColumn(name = "id", nullable = false)
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List<ActivityProperty> properties;

Обновление 05/06/2019 Поскольку вы включили каскадирование, вы должны и записать идентификатор из отношения, которое вы должны установитьполе ActivityId только для чтения:

@Column(name = "id", insertable = false, updateable = false)
private Integer activityId;

Подробнее о сопоставлениях отношений здесь: https://thoughts -on-java.org / ultimate-guide-association-mappings-jpa-hibernate /

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...