Два практически идентичных OneToOne в Entity, пользовательская аннотация соединения - PullRequest
2 голосов
/ 28 марта 2012

У меня есть отношения между родителями и детьми OneToOne, но два из них. Аннотации не очень хороши, но выдает хорошую схему БД, но код не работает. Если я пытаюсь сохранить экземпляр Parent, Hibernate сначала пытается сохранить child1 и child2 - но он нарушает FK, определенный в Child ->, поскольку владелец еще не существует в БД ... Поэтому мне нужно сохранить Parent, а затем child1, и child2.

Если бы я мог сделать это, это не помогло, потому что, когда я пытаюсь загрузить Parent, Hibernate не будет знать, какая запись в таблице Child принадлежит child1 или child2 ... Так что в этом случае мне нужно будет указать одно условие в соединении для child1 что-то типа "где тип = 1" и для child2 "где тип = 2" ...

Просто чтобы уточнить: в таблице Child будет ноль или один ребенок на одного Parent с ChildType.A (всегда child1) и ноль или один ребенок с ChildType.B (всегда child2).

Мне нужно сохранить XML, который выглядит следующим образом:

<parent id="" oid="">
   <child1 (and other attributes)>
   <child2 (and other attributes)>
<parent>

Оба элемента child1 и child2 имеют одинаковый тип, поэтому имеют тип Child в классах Java. Разница только в названии элемента (в Java я различаю их с ChildType). Только идентификация для детей - это атрибуты id и oid от родителя. Они указывают на другой Parent, следовательно, target в Child.

Мне нужно как-то изменить аннотации, чтобы это работало ... У вас, ребята, есть идеи, потому что я действительно застрял ???

Parent.java

public class Parent {

    private String oid
    private Long id;

    private Child child1;
    private Child child2;

    @Id
    @GeneratedValue(generator = "IdGenerator")
    @GenericGenerator(name = "IdGenerator", strategy = "com.example.IdGenerator")
    @Column(name = "id")
    public Long getId() {
        return id;
    }

    @Id
    @GeneratedValue(generator = "OidGenerator")
    @GenericGenerator(name = "OidGenerator", strategy = "com.example.OidGenerator")
    @Column(name = "oid", nullable = false, updatable = false, length = 36)
    public String getOid() {
        return oid;
    }

    @OneToOne(optional = true, fetch = FetchType.EAGER)
    @Cascade({org.hibernate.annotations.CascadeType.ALL})
    public Child getChild1() {
        return child1;
    }

    @OneToOne(optional = true, fetch = FetchType.EAGER)
    @Cascade({org.hibernate.annotations.CascadeType.ALL})
    public Child getChild2() {
        return child2;
    }
}

Child.java

public class Child {

    private Parent owner;
    private String ownerOid;
    private Long ownerId;
    private ChildType type;

    private Parent target;

    @MapsId("owner")
    @ManyToOne(fetch = FetchType.LAZY)
    @PrimaryKeyJoinColumns({
            @PrimaryKeyJoinColumn(name = "owner_oid", referencedColumnName = "oid"),
            @PrimaryKeyJoinColumn(name = "owner_id", referencedColumnName = "id")
    })
    public Parent getOwner() {
        return owner;
    }

    @MapsId("target")
    @ManyToOne(fetch = FetchType.LAZY)
    @PrimaryKeyJoinColumns({
            @PrimaryKeyJoinColumn(name = "target_oid", referencedColumnName = "oid"),
            @PrimaryKeyJoinColumn(name = "target_id", referencedColumnName = "id")
    })
    public Parent getTarget() {
        return target;
    }

    @Id
    @Column(name = "owner_id")
    public Long getOwnerId() {
        if (ownerId == null && owner != null) {
            ownerId = owner.getId();
        }
        return ownerId;
    }

    @Id
    @Column(name = "owner_oid", length = 36)
    public String getOwnerOid() {
        if (ownerOid == null && owner != null) {
            ownerOid = owner.getOid();
        }
        return ownerOid;
    }

    @Id
    @Column(name = "target_id")
    public Long getTargetId() {
        if (targetId == null && target != null) {
            targetId = target.getId();
        }
        return targetId;
    }

    @Id
    @Column(name = "target_oid", length = 36)
    public String getTargetOid() {
        if (targetOid == null && target != null) {
            targetOid = target.getOid();
        }
        if (targetOid == null) {
            targetOid = "";
        }
        return targetOid;
    }

    @Id
    @Enumerated(EnumType.ORDINAL)
    public ChildType getType() {
        if (type == null) {
            return ChildType.A;
        }
        return type;
    }
}

ChildType.java

public enum ChildType {
    A, B;
}

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

Ответы [ 3 ]

2 голосов
/ 05 апреля 2012

Я вижу две проблемы с использованием однозначных ссылок:

  1. У родителя ноль или один ребенок1, и ноль или один ребенок2.Насколько я понимаю, вы используете один к одному, когда он ВСЕГДА один.
  2. Ваши идентификаторы становятся действительно сложными.Идея «один-к-одному» заключается в том, что идентификаторы одинаковы для двух сущностей, и у вас есть Parent с первичным ключом из двух частей и Child с другим первичным ключом из четырех частей.

Другим дополнением к 2. является то, что если вы используете 1-1, то ID родителя и ребенка должны совпадать.Но если у родителя двое детей, они не могут иметь одинаковый идентификатор!Так что есть реальная проблема моделирования данных.Кроме того, тот факт, что дочерний тип является частью ключа, также является неприятным запахом, потому что я ожидаю, что дочерний тип является частью вашей бизнес-логики,

Я думаю, что вам нужно то, что Hibernate называет «однонаправленнымto-one ассоциация по внешнему ключу.В руководстве Hibernate ORM показано, как это сделать в XML, вот пример с аннотациями, хотя и без уникального набора свойств.

Я собираюсь предположить, что естьнет никакой внешней бизнес-причины, по которой ваши первичные ключи должны быть такими, какие они есть, и мы предлагаем, чтобы вы

  1. изменили Parent и Child, чтобы каждый из них имел первичный ключ одного поля, используя @Id и @GeneratedValue (стратегии = GenerationType.AUTO).
  2. Измените Parent, чтобы ссылки на child1 и child2 имели значение @ManyToOne (unique = true)
  3. Если вы хотите, измените Child, как описано в Hibernate.руководство, чтобы оно содержало ссылку на Parent.

Вы потеряете «особенность», заключающуюся в том, что родители и дети имеют одинаковые идентификаторы, но получите огромную простоту в вашей схеме идентификаторов объектов, что ускоряет работу с базами данных.и код проще для чтения.

Если есть внешняя причина, по которой вы не указали, почему Родителю и / или Ребенку нужен первичный элемент, состоящий из нескольких частей.Да, этот ответ потребует некоторой модификации.

Я также подкрепляю мысль, которую @barsju сделал для использования наследования или даже полностью независимых классов для выполнения двух дочерних типов.Если вы используете ID так, как я изложил здесь, вы можете сделать так, чтобы у Parent были ссылки на конкретные подтипы, и запрос будет работать нормально.

2 голосов
/ 28 марта 2012

слишком много вещей, которые я не совсем понимаю в вашем решении, чтобы дать хороший ответ, но только некоторые мысли:

Попробуйте использовать наследование вместо перечисления ChildType. Таким образом, у вас будут ChildA и ChildB, расширяющие Child.

Таким образом, ваш родитель может иметь:

private ChildA child1;
private ChildB child2;

Вместо составного первичного ключа я хотел бы рассмотреть возможность использования уникального автоматически сгенерированного ключа, а затем добавить уникальное ограничение на другие поля id и oid. Это должно упростить отношения между родителями и дочерними элементами, и у вас могут быть разные родительские реализации для ChildA и ChildB:

In ChildA:
@OneToOne(mappedBy="child1")
public Parent getParent() {
    return parent;
}

А

In ChildB:
@OneToOne(mappedBy="child2")
public Parent getParent() {
    return parent;
}

А у ребенка просто:

public abstract Parent getParent();

Теперь все, что я до сих пор не совсем уловил: родитель / владелец / цель.

1 голос
/ 04 апреля 2012

Используйте @JoinColumns и определите фильтр

In Parent.java:

@OneToOne(optional = true, fetch = FetchType.EAGER)
@JoinColumns ({
    @JoinColumn(name="id", referencedColumnName = "owner_id"),
    @JoinColumn(name="oid", referencedColumnName = "owner_oid"),
})
@FilterJoinTable(name="type", condition="type = 'A'")
@Cascade({org.hibernate.annotations.CascadeType.ALL})
public Child getChild1() {
    return child1;
}

@OneToOne(optional = true, fetch = FetchType.EAGER)
@JoinColumns ({
    @JoinColumn(name="id", referencedColumnName = "owner_id"),
    @JoinColumn(name="oid", referencedColumnName = "owner_oid"),
})
@FilterJoinTable(name="type", condition="type = 'B'")
@Cascade({org.hibernate.annotations.CascadeType.ALL})
public Child getChild2() {
    return child2;
}

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

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

...