2010-04-09 2 views
22

Я использую Hibernate с аннотациями (весной), и у меня есть объект, который имеет упорядоченную взаимосвязь «один-один», которая представляет собой дочерний объект, который имеет составной первичный ключ, один компонент которого является внешним ключом назад к идентификатору родительского объекта.@OneToMany и составные первичные ключи?

структура выглядит следующим образом:

+=============+     +================+ 
| ParentObj |     | ObjectChild | 
+-------------+ 1   0..* +----------------+ 
| id (pk)  |-----------------| parentId  | 
| ...   |     | name   | 
+=============+     | pos   | 
           | ...   | 
           +================+ 

Я пробовал различные комбинации аннотаций, ни один из которых, кажется, работает. Это ближайший я смог придумать:

@Entity 
public class ParentObject { 
    @Column(nullable=false, updatable=false) 
    @Id @GeneratedValue(generator="...") 
    private String id; 

    @OneToMany(mappedBy="parent", fetch=FetchType.EAGER, cascade={CascadeType.ALL}) 
    @IndexColumn(name = "pos", base=0) 
    private List<ObjectChild> attrs; 

    ... 
} 

@Entity 
public class ChildObject { 
    @Embeddable 
    public static class Pk implements Serializable { 
     @Column(nullable=false, updatable=false) 
     private String parentId; 

     @Column(nullable=false, updatable=false) 
     private String name; 

     @Column(nullable=false, updatable=false) 
     private int pos; 

     @Override 
     public String toString() { 
      return new Formatter().format("%s.%s[%d]", parentId, name, pos).toString(); 
     } 

     ... 
    } 

    @EmbeddedId 
    private Pk pk; 

    @ManyToOne 
    @JoinColumn(name="parentId") 
    private ParentObject parent; 

    ... 
} 

Я прибыл на это после длительного боя экспериментов, в которых большинство из моих других попыток давали объекты, которые зимуют не могли даже загружать по разным причинам ,

ОБНОВЛЕНИЕ: Спасибо всем за комментарии; Я добился определенного прогресса. Я сделал несколько настроек, и я думаю, что это ближе (я обновил код выше). Теперь, однако, проблема заключается в вставке. Родительский объект, кажется, сохраняет штраф, но дочерние объекты не сохраняются, и то, что я смог определить, заключается в том, что спящий режим не заполняет родительскую часть первичного ключа (составного) дочерних объектов, м получение не-уникальная ошибки:

org.hibernate.NonUniqueObjectException: 
    a different object with the same identifier value was already associated 
    with the session: [org.kpruden.ObjectChild#null.attr1[0]] 

Я заполнение name и pos атрибутов в моем собственном коде, но, конечно, я не знаю, родительский идентификатор, так как он не сохранен. Любые идеи о том, как убедить спящий режим заполнить это?

Спасибо!

+2

Удачи. Проект, над которым я был, должен был полностью отказаться от составных клавиш в пользу синтетических ключей из-за плохой поддержки Hibernate. –

+0

Исключение может быть вызвано использованием List <>. Вы должны использовать Set <>, чтобы избежать дублирования ключей, используя hashCode и равное определение. Hibernate рассматривает два «логически идентичных» элемента в списке как два элемента, а затем вызывает исключение. – Popeye

ответ

2

После долгих экспериментов и разочарований я в конце концов решил, что не могу делать то, что хочу.

В конечном счете, я пошел вперед и дал ребенку объект свой собственный синтетический ключ и позволить Hibernate управлять им. Это не идеально, так как ключ почти такой же большой, как и остальные данные, но он работает.

+0

'@JoinColumns ({ @JoinColumn (name = "userfirstname_fk", referatedColumnName = "firstName"), @ JoinColumn (name = "userlastname_fk", referendedColumnName = "lastName") }) ' – ahaaman

+1

Хорошее решение. Попытка сделать что-либо, кроме базового материала в Hibernate, требует слишком много интуитивного вовлечения. Я обычно делаю то же самое - дайте ВСЕ первичный - это упрощает понимание Hibernate. – jasop

15

В книге Manning Java Persistence with Hibernate приведен пример, описывающий, как это сделать в разделе 7.2. К счастью, даже если вы не владеете книгой, вы можете увидеть пример исходного кода, загрузив версию JPA проекта образца Caveat Emptor (прямая ссылка here) и изучив классы Category и CategorizedItem в пакете auction.model.

Я также подытожу ключевые аннотации ниже. Дайте мне знать, если это все равно.

ParentObject:

@Entity 
public class ParentObject { 
    @Id @GeneratedValue 
    @Column(name = "parentId", nullable=false, updatable=false) 
    private Long id; 

    @OneToMany(mappedBy="parent", fetch=FetchType.EAGER) 
    @IndexColumn(name = "pos", base=0) 
    private List<ObjectChild> attrs; 

    public Long getId() { return id; } 
    public List<ObjectChild> getAttrs() { return attrs; } 
} 

ChildObject:

@Entity 
public class ChildObject { 
    @Embeddable 
    public static class Pk implements Serializable { 
     @Column(name = "parentId", nullable=false, updatable=false) 
     private Long objectId; 

     @Column(nullable=false, updatable=false) 
     private String name; 

     @Column(nullable=false, updatable=false) 
     private int pos; 
     ... 
    } 

    @EmbeddedId 
    private Pk id; 

    @ManyToOne 
    @JoinColumn(name="parentId", insertable = false, updatable = false) 
    @org.hibernate.annotations.ForeignKey(name = "FK_CHILD_OBJECT_PARENTID") 
    private ParentObject parent; 

    public Pk getId() { return id; } 
    public ParentObject getParent() { return parent; } 
} 
+0

К сожалению, это не помогло: я получаю ту же ошибку. Единственные изменения, которые я вижу из того, что у меня есть, - это добавление атрибута @ForeignKey и добавление {insertable, updatable} = false в родительский атрибут. Есть ли что-то еще, чего я не вижу? Спасибо! –

+0

Есть еще несколько изменений. (1) Попробуйте изменить тип данных parentId на Long; (2) Я считаю, что геттеры необходимы; (3) уделять пристальное внимание именам столбцов для ParentObject # id, ParentObject # attrs, ChildObject.Pk # objectId и родительскому элементу ChildObject #; (4) Поле id ParentObject нуждается в аннотации @Id (и @GeneratedValue, если вы не вручную поставляете идентификатор); (5) Я переименовал ChildObject # pk в ChildObject # id, хотя я не думаю, что это изменение было необходимо. Если у вас есть все это, вы можете указать код чтения, который бросает какой-то окружающий контекст? – RTBarnard

+0

Re (2): Getters и seters не нужны. Hibernate может создавать прокси-серверы для выполнения необходимых действий. Я считаю, что лучше всего рассмотреть эту «магию». – John

2

Во-первых, в ParentObject, "исправить" атрибут mappedBy, который должен быть установлен в "parent". Кроме того (но это, возможно, опечатка) добавить @Id аннотацию:

@Entity 
public class ParentObject { 
    @Id 
    @GeneratedValue 
    private String id; 

    @OneToMany(mappedBy="parent", fetch=FetchType.EAGER) 
    @IndexColumn(name = "pos", base=0) 
    private List<ObjectChild> attrs; 

    // getters/setters 
} 

Затем в ObjectChild, добавить атрибут к objectIdname в композиционном ключе:

@Entity 
public class ObjectChild { 
    @Embeddable 
    public static class Pk implements Serializable { 
     @Column(name = "parentId", nullable = false, updatable = false) 
     private String objectId; 

     @Column(nullable = false, updatable = false) 
     private String name; 

     @Column(nullable = false, updatable = false) 
     private int pos; 
    } 

    @EmbeddedId 
    private Pk pk; 

    @ManyToOne 
    @JoinColumn(name = "parentId", insertable = false, updatable = false) 
    private ParentObject parent; 

    // getters/setters 

} 

И также добавить insertable = false, updatable = false до @JoinColumn, потому что мы повторяем столбец parentId при отображении этого объекта.

С этими изменениями постоянное и чтение объектов работает для меня отлично (проверено с помощью Derby).

+0

Просто потратил полчаса, пытаясь решить эту проблему, и моя проблема заключалась в том, что моя устаревшая база данных имела внешний ключ в таблице как «forecastId», и это то, что я скопировал в мою аннотацию JPA и по неизвестным причинам Spring решила, что колонка должна быть «прогнозом_ид» в базе данных, и она упала. Должен быть алгоритм автоматического определения столбцов, которые были немного неправильными. Я изменил '@Column (name =' to "forecastid" (все в нижнем регистре) и Spring решил вести себя. – Adam

0

Кажется, что у вас довольно близко, и я пытаюсь сделать то же самое в своей текущей системе. Я начал с суррогатного ключа, но хотел бы удалить его в пользу составного первичного ключа, состоящего из PK родителя и индекса в списке.

Я был в состоянии получить отношение один-к-одному, который разделяет ПК от основной таблицы, используя «чужой» генератор:

@Entity 
@GenericGenerator(
    name = "Parent", 
    strategy = "foreign", 
    parameters = { @Parameter(name = "property", value = "parent") } 
) 
public class ChildObject implements Serializable { 
    @Id 
    @GeneratedValue(generator = "Parent") 
    @Column(name = "parent_id") 
    private int parentId; 

    @OneToOne(mappedBy = "childObject") 
    private ParentObject parentObject; 
    ... 
} 

Интересно, если вы могли бы добавить @GenericGenerator и @ GeneratedValue для решения проблемы Hibernate, не назначающей недавно приобретенную PK родителя во время вставки.

0

После сохранения родительского объекта вы должны явно установить parentId в объектах Child, чтобы вставки на объектах Child работали.

1

Вы должны включить ParentObject ссылки только на ChildObject.Pk вместо карты родителей и ParentID отдельно:

(добытчики, сеттера, Hibernate атрибутов не связанные с проблемными и доступом члена ключевых слов опущенных)

class ChildObject { 
    @Embeddable 
    static class Pk { 
     @ManyToOne... 
     @JoinColumn(name="parentId") 
     ParentObject parent; 

     @Column... 
     String name... 
     ... 
    } 

    @EmbeddedId 
    Pk id; 
} 

В ParentObject вы просто положили @OneToMany(mappedBy="id.parent"), и он работает.

1

Обнаружили этот вопрос, ища ответ на его проблему, но это ответы не помогли решить мою проблему, потому что я искал @OneToMany, что не так хорошо подходит для структуры таблицы, в которой я шел. @ElementCollection подходит мне в моем случае. Одна из причин этого, я считаю, это то, что он рассматривает всю строку отношений как уникальную, а не только идентификатор строк.

@Entity 
public class ParentObject { 
@Column(nullable=false, updatable=false) 
@Id @GeneratedValue(generator="...") 
private String id; 

@ElementCollection 
@CollectionTable(name = "chidren", joinColumns = @JoinColumn(name = "parent_id")) 
private List<ObjectChild> attrs; 

... 
} 

@Embeddable 
public static class ObjectChild implements Serializable { 
    @Column(nullable=false, updatable=false) 
    private String parentId; 

    @Column(nullable=false, updatable=false) 
    private String name; 

    @Column(nullable=false, updatable=false) 
    private int pos; 

    @Override 
    public String toString() { 
     return new Formatter().format("%s.%s[%d]", parentId, name, pos).toString(); 
    } 

    ... getters and setters REQUIRED (at least they were for me) 
} 
0

Проведя три дня на этом, я думаю, я нашел решение, но если честно, мне не нравится это, и это, безусловно, может быть улучшена. Однако он работает и решает нашу проблему.

Вот ваш конструктор объектов, но вы также можете сделать это в методе setter. Кроме того, я использовал объект Collection, но она должна быть такой же или аналогичной со списком:

... 
public ParentObject(Collection<ObjectChild> children) { 
    Collection<ObjectChild> occ = new ArrayList<ObjectChild>(); 
    for(ObjectChild obj:children){ 
     obj.setParent(this); 
     occ.add(obj); 
    } 
    this.attrs = occ; 
} 
... 

В основном, как кто-то предложил, мы должны сначала вручную установить все детский родительский идентификатор перед сохранением родителя (вместе со всеми дети)

Смежные вопросы