Подтвердить что ты не робот

@OneToMany и составные первичные ключи?

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

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

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

    ...
}

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

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

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

Спасибо!

4b9b3361

Ответ 1

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

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

Ответ 2

В книге Мэннинга Java Persistence с 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; }
}

Ответ 3

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

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

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

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

    @EmbeddedId
    Pk id;
}

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

Ответ 4

Во-первых, в 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).

Ответ 5

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

Ответ 6

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

Мне удалось получить отношение "один к одному", которое разделяет ПК с главной таблицей с помощью "чужого" генератора:

@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, не назначающей родительский недавно приобретенный ПК во время вставки.

Ответ 7

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

Ответ 8

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

Вот ваш конструктор сущности, но вы также можете сделать это в методе 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;
}
...

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