Гибернации многие-ко-многим присоединиться к таблице не сохраняются унаследованные лица


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

У меня есть структура базы данных, в которой я пытаюсь сопоставить отношения "многие ко многим" через Hibernate. Идея состоит в том, что у меня есть несколько сущностей, которые могут конфликтовать друг с другом. С точки зрения структуры таблицы, каждая из этих сущностей подобна, так что Я абстрагировал их от общего сохраняемого класса, AbstractEntity. Поскольку сущности могут конфликтовать и могут конфликтовать с любым количеством других сущностей, я определил отдельную сущность Conflict Hibernate для определения каждого из конфликтов, которая должна быть сопоставлена через отношение "многие ко многим" с каждой из сущностей, находящихся в этом конфликте.

Вот объявления сущностей. Я включил Event в качестве примера конкретной реализации объекта AbstractEntity.

Мои Классы и конфигурация гибернации:

@Entity
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public abstract class AbstractEntity {
    protected Set<Conflict> conflicts = null;

    protected AbstractEntity() {
        conflicts = new HashSet<Conflict>();
    }

    @ManyToMany(fetch = FetchType.LAZY,
            targetEntity = Conflict.class)
    @JoinTable(name = "conflict_affected_entity",
            joinColumns = { @JoinColumn(name = "affected_entity_id") }, 
            inverseJoinColumns = { @JoinColumn(name = "conflict_id") })
    public Set<Conflict> getConflicts() {
        Hibernate.initialize(conflicts);
        return conflicts;
    }

    public void setConflicts(Set<Conflict> conflicts) {
        this.conflicts.clear();
        this.conflicts.addAll(conflicts);
    }
}

@Entity
@Table(name = "event")
public class Event extends AbstractEntity {

    private String name;

    public Event() {
        super();
    }

    @Column(name = "name", nullable = false)
    public String getName() {
        return name;
    }

    public void setName(String text) {
        this.name = text;
    }
}

@Entity
@Table(name = "conflict")
public class Conflict {

    private Set<AbstractEntity> affectedEntities = null;

    public Conflict() {
        affectedEntities = new HashSet<AbstractEntity>();
    }

    @ManyToMany(fetch = FetchType.LAZY,
            targetEntity = AbstractEntity.class,
            mappedBy = "conflicts")
    public Set<AbstractEntity> getAffectedEntities() {
        Hibernate.initialize(affectedEntities);
        return affectedEntities;
    }

    public void setAffectedEntities(Set<AbstractEntity> affectedEntities) {
        this.affectedEntities.clear();
        this.affectedEntities.addAll(affectedEntities);
    }
}

В коде мне нужно иметь возможность создавать записи соединительной таблицы с любой стороны (либо добавляя абстрактность к конфликту, либо добавляя конфликт к абстрактности). Пример того, как я создаю пример, показан здесь:

Event event1 = EventDAO.getInstance().get(eventID1);
Event event2 = EventDAO.getInstance().get(eventID2);
Conflict conflict = new Conflict();
conflict.getAffectedEntities().add(event1);
conflict.getAffectedEntities().add(event2);
Похоже, у гибернации есть ключ к пониманию того, что происходит, поэтому я чувствую, что упускаю что-то простое. Когда я создаю новый Conflict и добавляю к нему AbstractEntity, конфликт и абстрактность создаются, но соединительный стол остается пустым. Может ли кто-нибудь подсказать мне, что мне нужно сделать, чтобы заставить Hibernate заполнить пустую таблицу присоединения?
3 2

3 ответа:

Есть ли у ваших таблиц первичные ключи?

Измените отображение @ManyToMany следующим образом:

@JoinTable(name = "conflict_affected_entity",
        joinColumns = { @JoinColumn(name = "affected_entity_id", referencedColumnName="primaryKeyOfAffectedEntityColumnName") }, 
        inverseJoinColumns = { @JoinColumn(name = "conflict_id", referencedColumnName="primaryKeyOfConflictColumnName") })

Исходя из кода, который вы показали, включая то, как вы сохраняете данные, проблема заключается в том, какой сущности принадлежит отношение. В ваш код, сопоставления @ManyToMany в вашем классе AbstractEntity, что означает, что AbstractEntity и его подклассы являются те, которые имеют собственные связи, и несут ответственность за обновление баз для присоединяемой таблицы. В своем классе конфликтов вы правильно определили отношение "многие ко многим" и с помощью

mappedBy = "conflicts"

У вас есть сказал Hibernate, что это второе определение отношения "многие ко многим" на самом деле относится к отношению "многие ко многим", которое уже отображено атрибутом conflicts в классе AbstractEntity. Следовательно, это второе определение отношения "многие ко многим" не требует выполнения обновлений базы данных, поскольку обновления базы данных принадлежат абстрактности.

В коде, который вы показали для сохранения данных, у вас есть объекты событий, которые уже являются постоянными. Затем вы создаете новый класс конфликтов и добавьте отношения к этому классу. Проблема заключается в том, что при сохранении этого нового класса Hibernate не будет сохранять связь, поскольку в определении "многие ко многим" говорится, что объекты событий владеют обновлениями базы данных. Следовательно, чтобы устранить проблему, вы можете либо изменить отображение так, чтобы оно было объявлено в классе Conflict, и абстрактность объявляет аналог с помощью атрибута "mappedBy", либо вы можете сохранить классы конфликтов, а затем определить отношения, использующие классы событий, и обновлять их. Что-то вроде:

Event event1 = EventDAO.getInstance().get(eventID1);
Event event2 = EventDAO.getInstance().get(eventID2);
Conflict conflict = new Conflict();
session.save(conflict);
event1.getConflicts().add(conflict);
session.update(event1);
event2.getConflicts().add(conflict);
session.update(event2);

Оказывается, что проблема не была в аннотациях Hibernate. Вместо этого проблема заключалась в том, как я получал доступ к коллекциям в аннотированных методах.

Как вы можете видеть в вопросе, установщики коллекции очищают коллекцию, а затем добавляют любые элементы в новую коллекцию. Обновленный код (который работает!) показано здесь:

@ManyToMany(targetEntity = AbstractEntity.class,
        fetch = FetchType.LAZY)
@JoinTable(name = "conflict_affected_entity",
        joinColumns = { @JoinColumn(name = "conflict_id", referencedColumnName = "id") },
        inverseJoinColumns = { @JoinColumn(name = "affected_entity_id", referencedColumnName = "id") })
public Set<AbstractEntity> getAffectedEntities()
{
    Hibernate.initialize(affectedEntities);
    return affectedEntities;
}

public void setAffectedEntities(Set<AbstractEntity> affectedEntities)
{
    this.affectedEntities = affectedEntities;
}

И

@ManyToMany(targetEntity = Conflict.class,
        fetch = FetchType.LAZY)
@JoinTable(name = "conflict_affected_entity",
        joinColumns = { @JoinColumn(name = "affected_entity_id", referencedColumnName = "id") }, 
        inverseJoinColumns = { @JoinColumn(name = "conflict_id", referencedColumnName = "id") })
public Set<Conflict> getConflicts()
{
    Hibernate.initialize(conflicts);
    return conflicts;
}

public void setConflicts(Set<Conflict> conflicts)
{
    this.conflicts = conflicts;
}

Для будущих зрителей эта конфигурация гибернации (отображение каждой стороны как ManyToMany) создает два однонаправленных ассоциации: от конфликта - > абстрактности и от абстрактности - > конфликты. Это означает, что если вы решите использовать эту конфигурацию, вам придется быть осторожным при добавлении или удалении элементов из коллекций, чтобы убедиться, что записи присоединяемой таблицы обновляются, чтобы избежать нарушений ограничений внешнего ключа. Например, при удалении конфликта мы не можем просто сказать ConflictDAO.getInstance.delete(toDelete). Вместо этого мы должны убедиться, что конфликт не сохраняет никаких ассоциаций:

for (AbstractEntity affectedEntity : toDelete.getAffectedEntities()) {
    notifications.add(Notification.forUsersWithAccess(ActionType.UPDATE, affectedEntity));
    // Forcefully remove the associations from the affectedEntity to the Conflict, since we don't want to risk using CascadeType.DELETE
    affectedEntity.getConflicts().remove(toDelete);
}
ConflictDAO.getInstance().delete(toDelete);