Ограничение JPA 1.0 с использованием @IdClass с * вложенными * составными первичными ключами? - PullRequest
1 голос
/ 30 октября 2010

Приведен следующий пример (отделы - проекты):

Отдел имеет следующие свойства (составной первичный ключ):

@Entity
@IdClass(DeptId.class)
public class Department
{
    @Id
    @Column(name="number")
    private Integer number;

    @Id
    @Column(name="country")
    private String country;

    @Column(name="name")
    private String name;

    @OneToMany(mappedBy="dept")
    private Collection<Project> projects;

    ...
}

Здесь класс ПК:

public class DeptId implements Serializable
{
    private Integer number;
    private String country;

    ...
}

Отношения между проектами и отделами много-к-одному, то есть отдел может иметь много проектов. Класс Project сам использует составной ключ, ссылаясь на составной ключ Департамента. Важное примечание: речь идет только о реализации с @IdClass, а не @ EmbeddedId.

Тогда (проблемная) реализация JPA 1.0 @IdClass должна выглядеть примерно так (избыточные свойства deptNum и deptCtry): -> это просто уникальное имя в отделе

@Entity 
@IdClass(ProjectId.class)
public class Project
{
    @Id
    @Column(name="dept_number")
    private Integer deptNumber;

    @Id
    @Column(name="dept_country")
    private String deptCountry;

    @Id
    @Column(name="name")
    private String name;

    @ManyToOne 
    @JoinColumns({
       @JoinColumn(name="dept_number", referencedColumnName="number"),
       @JoinColumn(name="dept_country", referencedColumnName="country")
    })    
    private Department dept;

    ...
}

Идентификатор проекта:

public class ProjectId implements Serializable
{
    private String name;
    private DeptId dept;

    ...
}

Проблема в том, что ни Hibernate, ни EclipseLink не знают, как сопоставить два избыточных свойства deptNum и deptCtry в Project со свойством dept в DeptId (или соответствующими им свойствами). -> MappingException и т. Д.

Мой вопрос:

Является ли это ограничением JPA 1.0, что таблицы с составными ключами, ссылающимися на другие составные ключи с реализациями @IdClass , как правило, НЕ будут работать , потому что реализация JPA просто не может знать, как отобразить эти поля

В качестве обходного пути вам придется использовать @EmbeddedId для этих классов или использовать синтаксис JPA 2.0 для аннотирования ассоциаций @XToX с @Id. Я просто хочу убедиться, что мой взгляд на это правильный.

Спасибо

Ответы [ 3 ]

3 голосов
/ 03 ноября 2010

Да, это ограничение JPA 1.0, исправленное в JPA 2.0.В новом JPA 2.0 вы можете поместить аннотацию ID в ваши отношения dept и полностью избежать использования избыточных атрибутов deptCountry и deptNumber, когда класс ключей использует вложенность.В JPA 1.0 только базовые сопоставления могут быть помечены как отдельные идентификаторы, что требует избыточных сопоставлений и некоторого кода для обеспечения правильного размещения значений / отношений в кеше при сохранении.Из-за избыточности, как упоминалось в других ответах, одно из сопоставлений для поля должно быть помечено как доступное только для чтения с помощью вставляемого / обновляемого = ложного.Это означает, что значение не будет объединено с кешем, поэтому изменения (например, при вставке, поскольку вы не можете изменить идентификатор объекта, если он существует) не будут отражены, если объект не будет обновлен из базы данных.Если вы пометите JoinColumns только для чтения, вам нужно будет получить значения из ссылочного отдела и поместить их в соответствующие базовые атрибуты id вручную, когда вы хотите сохранить проект.Но вы также можете пометить основные атрибуты только для чтения.Eclipselink в любом случае не будет иметь никаких проблем и будет правильно устанавливать значения полей, используя связанный объект dept (до тех пор, пока он задан до вызова persist для Project).Обратите внимание, что базовые атрибуты могут заполняться или не заполняться при повторном чтении проекта в другом контексте - это будет зависеть от того, обновляется ли сущность из базы данных или нет.Если они доступны только для чтения, они не будут объединены в общий кэш, поскольку они, будучи только для чтения, не должны были измениться.Таким образом, они могут быть просто проигнорированы, или, если они должны быть заполнены, сущность обновлена ​​или значения, установленные из dept в событии.

Эту же модель можно использовать повторно с помощью JPA2.0 @MapsId, который также будет поддерживать основные сопоставления, используя для вас значения из отношения.Единственным преимуществом, которое я вижу, является то, что вам не нужно получать доступ к отношениям (потенциально вызывающим ненужные объединения или доступ к базе данных на ленивых отношениях), чтобы получить значения полей внешнего ключа / идентификатора.

Что касается исключений ZipArea EclipseLink, они связаны с тем, что ZipAreaId имеет zip-атрибут ZipId, а не выравнивается.JPA 1.0 требует, чтобы класс ключей имел атрибут того же типа и имени для каждого атрибута @ID в сущности.

2 голосов
/ 03 ноября 2010

Проблема с размещенным кодом заключается в том, что JPA 1.0 действительно не допускает вложения составных классов первичного ключа.Этот ProjectId недействителен:

public class ProjectId implements Serializable
{
    private String name;
    private DeptId dept;

    ...
}

DeptId должен быть сведен, например:

public class ProjectId implements Serializable
{
    private Integer deptNumber;
    private String deptCountry;
    private String name;

    ...
}

Я только что получил версию EclipseLink, но Hibernate имеет проблемы с этим.Интересно, как сказать Hibernate, что предполагается использование JPA 1.0.

2 голосов
/ 31 октября 2010

Проблема в том, что ни Hibernate, ни EclipseLink не знают, как сопоставить два избыточных свойства deptNum и deptCtry в Project со свойством dept в DeptId

Вот почему вам необходимо определить ManyToOne внешний ключ (и) как только для чтения с этим типом отображения.Это делается путем установки атрибутов JoinColumn insertable и updatable на false.

Поэтому попробуйте следующее:

@Entity 
@IdClass(ProjectId.class)
public class Project
{
    @Id
    @Column(name="dept_number")
    private Integer deptNumber;

    @Id
    @Column(name="dept_country")
    private String deptCountry;

    @Id
    @Column(name="name")
    private String name;

    @ManyToOne 
    @JoinColumns({
       @JoinColumn(name="dept_number", referencedColumnName="number", insertable=false, updatable=false),
       @JoinColumn(name="dept_country", referencedColumnName="country", insertable=false, updatable=false)
    })    
    private Department dept;
    ...
}
...