【问题标题】:Hibernate Many-To-Many join table not persisting for inherited EntityHibernate 多对多连接表不会为继承的实体持久化
【发布时间】:2014-02-18 20:37:20
【问题描述】:

我已阅读有关多对多关系的Hibernate Documentation,并尝试遵循他们的建议,但我的解决方案并未成功。希望有人能对这个话题有所了解。

我有一个数据库结构,我试图在其中通过 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);
    }
}

在代码中,我需要能够从任一侧创建连接表条目(将 AbstractEntity 添加到 Conflict 或将 Conflict 添加到 AbstractEntity)。此处显示了我如何创建示例的示例:

Event event1 = EventDAO.getInstance().get(eventID1);
Event event2 = EventDAO.getInstance().get(eventID2);
Conflict conflict = new Conflict();
conflict.getAffectedEntities().add(event1);
conflict.getAffectedEntities().add(event2);

Hibernate 似乎对正在发生的事情有一个线索,所以我觉得我错过了一些简单的东西。当我创建一个新的Conflict 并向其中添加一个AbstractEntity 时,会创建 Conflict 和 AbstractEntity,但连接表仍然为空。谁能给我一个线索,让我知道我需要做什么才能让 Hibernate 填充空白的连接表?

【问题讨论】:

  • 您的级联设置是什么?您调用的是哪些实体保存?使用如上所示的代码,我很惊讶抽象实体在添加到冲突实体时仍然存在,因为它似乎没有在关系上设置级联?
  • @Hedley - 看起来 Hibernate 文档指定 PERSISTMERGE 是默认配置的。我也尝试过指定ALL,但仍然没有运气。我编辑了问题以包含我用来提交新冲突的代码

标签: java hibernate inheritance hibernate-annotations


【解决方案1】:

根据您显示的代码,包括您如何保存数据,问题在于哪个实体拥有该关系。在您的代码中,您已在 AbstractEntity 类中映射了 @ManyToMany,这意味着 AbstractEntity 及其子类是拥有关系的对象,并负责连接表的数据库更新。在您的 Conflict 类中,您已经正确定义了多对多关系,并通过使用

mappedBy = "conflicts"

您已经告诉 Hibernate,多对多关系的第二个定义实际上是指已经由 AbstractEntity 类中的冲突属性映射的多对多关系。因此,多对多关系的第二个定义不需要执行数据库更新,因为数据库更新归 AbstractEntity 所有。

在您显示的用于保存数据的代码中,您拥有已经持久化的 Event 对象。然后创建一个新的 Conflict 类,并将关系添加到该类。问题是当你持久化这个新类时,Hibernate 不会持久化关系,因为多对多定义表明事件对象拥有数据库更新。因此,要解决此问题,您可以更改映射,使其在 Conflict 类中声明,然后 AbstractEntity 使用“mappedBy”属性声明对应对象,或者您可以保留 Conflict 类,然后使用事件类,并更新它们。比如:

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);

【讨论】:

    【解决方案2】:

    你的表有主键吗?

    像这样更改您的@ManyToMany 映射:

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

    【讨论】:

    • 我将 AbstractEntity 上的 @ManyToMany 映射更改为您建议的。我在两个表上都有一些真正无聊的id 字段作为主键,我在映射中使用了这些字段,但是休眠行为没有改变。尝试的映射是:`@JoinTable(name = "conflict_affected_entity", joinColumns = { @JoinColumn(name = "affected_entity_id", referencedColumnName = "id") }, inverseJoinColumns = { @JoinColumn(name = "conflict_id", referencedColumnName = "id ") })'
    【解决方案3】:

    事实证明问题不在 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;
    }
    

    对于未来的查看者,这种 Hibernate 配置(将每一边映射为 ManyToMany)会创建两个单向关联:来自 Conflict -> AbstractEntities 和来自 AbstractEntity -> Conflicts。这意味着如果您决定使用此配置,则在从集合中添加或删除项目时必须小心,以确保更新连接表条目以避免违反外键约束。例如,当删除一个冲突时,我们不能只说ConflictDAO.getInstance.delete(toDelete)。相反,我们必须确保 Conflict 不保留任何关联:

    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);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-05-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多