@ OneToMany и составные первичные ключи? - PullRequest
29 голосов
/ 10 апреля 2010

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

Структура выглядит примерно так:

+=============+                 +================+
| 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;

    ...
}

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

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

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 в своем собственном коде, но, конечно, я не знаю родительский идентификатор, потому что он еще не был сохранен. Любые идеи о том, как убедить Hibernate, чтобы заполнить это?

Спасибо!

Ответы [ 8 ]

14 голосов
/ 10 апреля 2010

Книга Мэннинга Сохранение Java с Hibernate содержит пример, описывающий, как это сделать в Разделе 7.2. К счастью, даже если вы не являетесь владельцем книги, вы можете увидеть пример исходного кода, загрузив JPA-версию примера проекта Caveat Emptor (прямая ссылка здесь ) и проверка классов 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<ChildObject> attrs;

   public Long getId () { return id; }
   public List<ChildObject> 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; }
}
6 голосов
/ 15 ноября 2013

Вы должны включить ссылку ParentObject только в ChildObject.Pk, а не отображать parent и 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"), и это работает.

2 голосов
/ 15 апреля 2010

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

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

2 голосов
/ 10 апреля 2010

Во-первых, в 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 добавьте атрибут name к objectId в составном ключе:

@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).

1 голос
/ 13 августа 2015

Нашел этот вопрос в поисках ответа на свою проблему, но его ответы не решили мою проблему, потому что я искал @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 голосов
/ 14 апреля 2017

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

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

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

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

0 голосов
/ 31 января 2013

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

0 голосов
/ 01 октября 2010

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

Мне удалось получить отношение один-к-одному, которое разделяет 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 родителя во время вставки.

...