【问题标题】:Is there a cleaner way of building a MappedSuperclass Tree class in Spring JPA?在 Spring JPA 中构建 MappedSuperclass Tree 类是否有更简洁的方法?
【发布时间】:2019-03-28 15:04:27
【问题描述】:

我目前有几个实体,它们的行为类似于树,需要将它们保存到数据库中。

所以为了避免重复代码,我构建了这个类:

@MappedSuperclass
public abstract class TreeStructure<T extends TreeStructure>
{
    @ManyToOne(cascade = CascadeType.PERSIST)
    private T  parent;

    @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
    protected Set<T> children = new HashSet<>();

    /**
     * Function that is used before deleting this entity. It joins this.children to this.parent and viceversa.
     */
    @Transactional
    @PreRemove
    public void preDelete()
    {
        unregisterInParentsChildren();

        while (!children.isEmpty())
        {
            children.iterator().next().setParent(parent);
        }

    }

    public abstract long getId();

    protected void setParent(T pParent)
    {
        unregisterInParentsChildren();
        parent = pParent;
        registerInParentsChildren();
    }

    /**
     * Register this TreeStructure in the child list of its parent if it's not null.
     */
    private void registerInParentsChildren()
    {
        getParent().ifPresent((pParent) -> pParent.children.add(this));
    }

    /**
     * Unregister this TreeStructure in the child list of its parent if it's not null.
     */
    private void unregisterInParentsChildren()
    {
        getParent().ifPresent((pParent) -> pParent.children.remove(this));
    }

    /**
     * Move this TreeStructure to an new parent TreeStructure.
     *
     * @param pNewParent the new parent
     */
    public void move(final T pNewParent)
    {
        if (pNewParent == null)
        {
            throw new IllegalArgumentException("New Parent required");
        }

        if (!isProperMoveTarget(pNewParent) /* detect circles... */)
        {
            throw new IllegalArgumentException(String.format("Unable to move Object %1$s to new Object Parent %2$s", getId(), pNewParent.getId()));
        }

        setParent(pNewParent);
    }

    private boolean isProperMoveTarget(TreeStructure pParent)
    {
        if (pParent == null)
        {
            return true;
        }
        if (pParent == this)
        {
            return false;
        }

        return isProperMoveTarget(pParent.parent);
    }

    public int getLevel()
    {
        return getParent().map(pParent -> pParent.getLevel() + 1).orElse(1);
    }

    /**
     * Return the <strong>unmodifiable</strong> children of this TreeStructure.
     *
     * @return the child nodes.
     */
    public Set<T> getChildren()
    {
        return Collections.unmodifiableSet(this.children);
    }

    public Optional<T> getParent()
    {
        return Optional.ofNullable(parent);
    }

    public Optional<Long> getParentCategoryId()
    {
        return parent == null ? Optional.empty() : Optional.of(parent.getId());
    }
}

然后要实际实现它,我只需这样做:

@Entity(name = "CATEGORY")
public class Category extends TreeStructure<Category>
{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @JsonProperty("category_id")
    private long id;

// etc...

据我所知,一切都像一个魅力,但每次我进入 TreeStructure 类 Intellij 都会突出一些错误:

mappedBy = "parent" -> 无法解析父属性。

children.iterator().next().setParent(parent) -> 作为原始类型 TreeStructure 的成员对 setParent(T) 的未经检查的调用

pParent.children.add(this) -> 未经检查地调用 add(E) 作为原始类型 java.util.Set 的成员

我也尝试不使用泛型,所以我可以只使用抽象 TreeStructure,然后从其他类扩展,但是我遇到了父/子问题,因为您无法从 OneToMany/ManyToOne 引用中引用 MappedSuperclass。

所以,终于说到重点了:有没有办法以更好/更清洁的方式实现它?这个警告是有意义的还是只是 Intellij 不够聪明?

【问题讨论】:

  • 可以忽略未检查的调用。 mappedBy = "parent" 表示无论您使用什么T,它都可能没有名为parent 的字段(我是这么理解的)

标签: java hibernate spring-boot intellij-idea spring-data-jpa


【解决方案1】:

问题不在于 JPA,而在于使用泛型。

首先,更改您的抽象类签名,使其具有递归类型:

public abstract class TreeStructure<T extends TreeStructure<T>>

接下来,您不能引用 'this',因为您不知道 'this' 的实现,因此您可以将其转换为 'T' 或添加具有如下签名的抽象方法:

public abstract T getImpl();

在实现中只返回“this”。

public T getImpl() {
  return this;
}

在侧节点上,访问类中的父类实例变量可能不是一个好主意。向 TreeStructure 类添加 addChild 和 removeChild 方法可能是一个更好的主意。

【讨论】:

  • 谢谢,这确实修复了 Unchecked 调用。不知道递归泛型,所以我一定会进一步调查。 :) 你知道是否有一种方法可以在不使用泛型的情况下实现这种行为?
  • 另一方面,我需要上下查询树的性能,所以我需要在每个实例中都有父级和子级。
  • 发现这个答案特别有用:stackoverflow.com/a/30667912/2560692 但是,不使泛型递归有哪些问题?据我所见,没有它,一切都运行良好。使用原始类型进行参数化有什么危险吗?
【解决方案2】:

我有一个非常相似的场景,我没有使用 T。相反,我只有抽象类,因为我不需要类型化孩子的灵活性,而且我没有强制转换。据我所知(共享代码),它可能会让您接地,但我不知道您是否有其他要求。

我的另一个区别是抽象类不是映射超类,而是@Inheritance(strategy = InheritanceType.SINGLE_TABLE)

如果有帮助,您可以在 this repository 中找到完整的工作示例

@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class TreeStructure {

    ...

    @ManyToOne(cascade = CascadeType.PERSIST)
    private TreeStructure  parent;

    @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
    protected Set<TreeStructure> children = new HashSet<>();

【讨论】:

  • 你确定这行得通吗?有人告诉我,您不能将 OneToMany 映射到 MappedSuperclass。
  • 你说得对...我的抽象类@Inheritance(strategy = InheritanceType.SINGLE_TABLE)我没用过@MappedSuperclass,也可能是你说的问题。我会更新我的答案
  • 如果我没记错的话,TreeStructure 的所有实现最终都在同一个表中,我也想避免 :)
  • 是的,这取决于您的业务逻辑。就我而言,这没关系,因为实体非常相似
  • 是的,可悲的是,对我来说根本不是这种情况。无论如何,感谢您的见解! :)
猜你喜欢
  • 2016-02-11
  • 2015-09-27
  • 2016-11-30
  • 1970-01-01
  • 2022-12-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-08
相关资源
最近更新 更多