【问题标题】:Using cascade.all in a many-to-many with extra columns association在具有额外列关联的多对多中使用 cascade.all
【发布时间】:2022-01-15 16:21:50
【问题描述】:

搜索了很长一段时间后,我想知道我的代码是否错误,或者在 Hibernate 中是否根本不可能。

我会用一个假的例子来解释我的问题。假设我的数据库中有三个表,post、posttag 和 tag。一个帖子可以有多个标签,一个标签可以用于我的多个帖子,所以它是一个多对多的关联,但在(posttag)之间的表中还有额外的列。

example entity relationship diagram

所以,在我的代码中,我有 3 个实体和一个用于 posttag 复合键的类。

邮政实体:

@Entity
@Table(name = "post", catalog = "fakeExample")
public class PostEntity implements Serializable
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "IDPOST", nullable = false)
    private Integer idpost;
    
    @OneToMany(mappedBy = "post", targetEntity = PostTagEntity.class, cascade = CascadeType.ALL, orphanRemoval = true)
    private List<PostTagEntity> listOfPostTag = new ArrayList<>();

    public void setIdpost(Integer idpost)
    {
        this.idpost = idpost;
    }

    public Integer getIdpost()
    {
        return this.idpost;
    }
    
    public void setListOfPostTag(List<PostTagEntity> listOfPostTag)
    {
        if (listOfPostTag != null)
        {
            this.listOfPostTag.clear();
            this.listOfPostTag.addAll(listOfPostTag);
        }
    }
    
    public List<PostTagEntity> getListOfPostTag()
    {
        return this.listOfPostTag;
    }
}

标签实体:

@Entity
@Table(name = "tag", catalog = "fakeExample")
public class TagEntity implements Serializable
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "IDTAG", nullable = false)
    private Integer idtag;
    
    @OneToMany(mappedBy = "tag", targetEntity = PostTagEntity.class, cascade = CascadeType.ALL, orphanRemoval = true)
    private List<PostTagEntity> listOfPostTag = new ArrayList<>();

    public void setIdtag(Integer idtag)
    {
        this.idtag = idtag;
    }

    public Integer getIdtag()
    {
        return this.idtag;
    }
    
    public void setListOfPostTag(List<PostTagEntity> listOfPostTag)
    {
        if (listOfPostTag != null)
        {
            this.listOfPostTag.clear();
            this.listOfPostTag.addAll(listOfPostTag);
        }
    }
    
    public List<PostTagEntity> getListOfPostTag()
    {
        return this.listOfPostTag;
    }
}

PostTagEntity:

@Entity
@Table(name = "posttag", catalog = "fakeExample")
public class PostTagEntity implements Serializable
{
    @EmbeddedId
    private PostTagEntityKey compositePrimaryKey = new PostTagEntityKey();
    
    @Column(name = "EXTRACOLUMN")
    private Integer extraColumn;
    
    @ManyToOne
    @JoinColumn(name = "IDPOST", referencedColumnName = "IDPOST", nullable = false)
    @MapsId("idpost")
    private PostEntity post;
    
    @ManyToOne
    @JoinColumn(name = "IDTAG", referencedColumnName = "IDTAG", nullable = false)
    @MapsId("idtag")
    private TagEntity tag;

    public void setIdpost(Integer idpost)
    {
        this.compositePrimaryKey.setIdpost(idpost);
    }
    
    public Integer getIdpost()
    {
        return this.compositePrimaryKey.getIdpost();
    }
    
    public void setIdtag(Integer idtag)
    {
        this.compositePrimaryKey.setIdtag(idtag);
    }
    
    public Integer getIdtag()
    {
        return this.compositePrimaryKey.getIdtag();
    }
    
    public void setExtraColumn(Integer extraColumn)
    {
        this.extraColumn = extraColumn;
    }
    
    public Integer getExtraColumn()
    {
        return this.extraColumn;
    }
    
    public void setPost(PostEntity post)
    {
        this.post = post;
    }
    
    public PostEntity getPost()
    {
        return this.post;
    }
    
    public void setTag(TagEntity tag)
    {
        this.tag= tag;
    }
    
    public TagEntity getTag()
    {
        return this.tag;
    }
}

PostTagEntityKey:

@Embeddable
public class PostTagEntityKey implements Serializable
{
    @Column(name = "IDPOST", nullable = false)
    private Integer idpost;
    @Column(name = "IDTAG", nullable = false)
    private Integer idtag;
    
    public PostTagEntityKey()
    {
    }
    
    public PostTagEntityKey(Integer idpost, Integer idtag)
    {
        this.idsalle = idsalle;
        this.idequipement = idequipement;
    }
    
    public void setIdpost(Integer value)
    {
        this.idpost = value;
    }
    
    public Integer getIdpost()
    {
        return this.idpost;
    }
    
    public void setIdtag(Integer value)
    {
        this.idtag = value;
    }
    
    public Integer getIdtag()
    {
        return this.idtag;
    }
    
    public boolean equals(Object obj)
    {
        if (this == obj)
        {
            return true;
        }
        if (obj == null)
        {
            return false;
        }
        if (this.getClass() != obj.getClass())
        {
            return false;
        }
        PostTagEntityKey other = (PostTagEntityKey) obj;
        if (this.idpost == null ? other.idpost != null : !this.idpost.equals((Object) other.idpost))
        {
            return false;
        }
        if (this.idpost == null ? other.idpost != null
                : !this.idtag.equals((Object) other.idtag))
        {
            return false;
        }
        return true;
    }
    
    public int hashCode()
    {
        final int prime = 31;
        int result = 1;
        
        result = prime * result + ((idpost == null) ? 0 : idpost.hashCode());
        result = prime * result + ((idtag == null) ? 0 : idtag.hashCode());
        
        return result;
    }
}

另外,我使用的是 Spring,所以这里是我执行插入或其他操作时涉及的少数类。我认为问题不是来自这里,而是以防万一。

邮政服务:

public interface PostService
{
    public List<PostEntity> findAll();
    
    public Optional<PostEntity> findById(int var1);
    
    public PostEntity save(PostEntity var1);
    
    public void deleteById(int var1);
}

PostImpl:

@Service
public class PostImpl implements PostService
{
    @Autowired
    private PostRepository repository;
    
    @Override
    public List<PostEntity> findAll()
    {
        return this.repository.findAll();
    }
    
    @Override
    public Optional<PostEntity> findById(int id)
    {
        return this.repository.findById(id);
    }
    
    @Override
    public PostEntity save(PostEntity toSave)
    {
        return (PostEntity) this.repository.save(toSave);
    }
    
    @Override
    public void deleteById(int id)
    {
        this.repository.deleteById(id);
    }
}

后存储库:

@Repository
public interface PostRepository extends JpaRepository<PostEntity, Integer>, JpaSpecificationExecutor<PostEntity>, PagingAndSortingRepository<PostEntity, Integer>
{
}

所以当我需要插入帖子时,我只需要这样:

@Autowired
PostService postService;
public PostEntity createPost(PostEntity post)
{
    return this.postService.save(post);
}

对我来说,Hibernate 的预期行为是:

  • 当我插入帖子时,插入帖子并将每个 postTag 插入 listOfPostTag 中
  • 当我更新帖子时,删除 listOfPostTag 中所有缺失的 postTag,添加 listOfPostTag 中的每个新 postTag,更新 listOfPostTag 中的更改 postTag 并更新帖子
  • 当我删除帖子时,删除 listOfPostTag 中的每个 postTag 并删除帖子

但是,当我尝试插入帖子时,我遇到了错误。从我所做的许多测试来看,Hibernate 似乎成功插入帖子,然后尝试插入 postTags,但由于 PostTagEntityKey 中的 idPost 仍然为空而失败。我本来希望 Hibernate 使用插入帖子中的 id 更新它。

所以我的问题是,在我描述的情况下,hibernate 可以做到这一点吗?还是我必须手动完成(不使用级联模式进行插入/更新)? 解释可能是复合键,双向,其他东西是不可能的,或者这不是hibernate应该做的事情。我想知道这是否可能,如果可能,我做错了什么?

如果不可能,我想知道如果你甚至不能为像中间表这样常​​见的东西做级联插入,那又有什么意义呢。

我没有尝试编写这个假示例,但我相信它会产生相同的结果,因为我几乎没有改变原始示例。我也跳过了创建 postEntity 的部分,因为在我的例子中它是从 JSON 解析的。我使用了调试器并尝试了不同的东西,所以我几乎可以肯定问题不是来自这里。每个字段都被填满,即使是 PostTagEntityKey 中的 idTag。只是 PostTagEntityKey 中的 idPost 为空,因为尚未插入帖子。插入帖子后,hibernate不会更新它。我开始相信级联模式没有办法更新它,也许就是这样。

编辑: 所以,感谢@a.ghavidel 的评论,我意识到我从未尝试将帖子设置为 posttag。

所以我在帖子实体中更改了 setListOfPostTag 函数,如下所示:

public void setListOfPostTag(List<PostTagEntity> listOfPostTag)
{
    if (listOfPostTag != null)
    {
        this.listOfPostTag.clear();
        for(PostTagEntity postTag : listOfPostTag)
        {
            postTag.setPost(this);
            this.listOfPostTag.add(postTag);
        }
    }
}

问题也发生了变化。在修改之前,它告诉我它无法插入 postTag 因为 idPost 为空。现在它告诉我“org.hibernate.PersistentObjectException:分离的实体传递给持久化:com.fakeexample.TagEntity”。

所以我认为,要检索主键,实体 postTag 需要设置变量 Post。 Post 中的列表 listOfPostTag 是不够的。所以我认为我原来的问题现在已经解决了。

我的新问题是休眠似乎认为 postTag 中的标签是“分离的”,我不确定这是什么意思。 在我的例子中,postTag 中的标签不是来自休眠的函数,它是从 json 解析的,所以也许这就是原因。但是我不需要它被持久化,理论上我只需要它的 id 来插入 postTag 并且 postTag 中没有级联。

我尝试将 CascadeType.All 替换为 CascadeType.Merge,因为有些人说它对他们有用,但是当我这样做时,它只是在我插入帖子时不插入任何 postTag。但是,当我更新帖子时,它似乎工作得很好。

我认为我非常接近解决方案。我将尝试一些事情,如果找到答案,我会编辑这篇文章。

编辑 2:

所以,我尝试了一些事情并取得了一些进展。该对象已分离,因为它尚未由休眠创建。合并过程中没有问题,但无法持久化。

解决方案可能是在 create 函数中手动完成所有操作...

但正确的解决方案是使用 entityManager 中的函数 getReference()。它不会生成任何不需要的选择或更新,它只是创建一个代理对象并且只需要参数中的 id。

但是,我认为在 spring 中无法访问 entityManager,但我们可以使用映射到 getReference() 方法的存储库中的函数 getOne()。基本上,如果我理解正确的话,函数 getOne() 应该是使用延迟加载,所以只要我们不需要加载对象就不会加载它。

我尝试使用此功能,并且确实我的代码现在可以正常工作。 但问题是:函数 getOne() 已弃用。

该函数已被 getById() 替换,但我真的不确定它是否也使用延迟加载,因为我已经大量使用此函数并且根本不创建代理对象。另外,我知道属性“fetch = FetchType.LAZY”不能放在@OneToMany,所以,如果我没有把它放在我的代码中,我想我没有使用延迟加载,它会做很多不需要的选择.另外我认为我也不应该一直使用延迟加载,我听说有时为集合的每个实体生成一个选择而不是延迟加载的单个选择可能会很麻烦......

所以我仍然需要进行一些研究以了解如何使用非弃用功能来做到这一点。

编辑 3: 好吧,不,我使用的函数是 findById 而不是 getById。所以 getById 可能是我的问题的解决方案。我要去检查文档以确认并测试它。

最终编辑: 好吧,它适用于插入,但我现在在更新期间遇到了问题。基本上它告诉我 posttag 已经存在于数据库中。可能是因为我用我自己通过解析创建的列表替换了 findById 中的 listOfPostTag。解决方案可能是编辑此列表中的对象。现在我了解了 hibernate 中的工作原理,我需要查看我的整个代码以应用这些原则。 最后,hibernate似乎不是很友好的restful api,我们必须处理每个对象,以便hibernate能够识别它们。

总结一下,我的问题是: 第一:一致性。我没有在 postTag 对象中设置帖子。 第二:附加和分离对象。我没有使用 getById 函数为 Taf 对象创建 hibernate 识别的对象。 第三:更新。当我更新帖子时,我没有更新 listOfPostTag 中的对象,我只是用另一个列表替换了它,只有 hibernate 无法识别的对象。我猜我应该按对象更新列表对象。

【问题讨论】:

    标签: java spring hibernate jpa


    【解决方案1】:

    你应该在PostEntity创建一个时使用这个方法

    private void addPostTag(PostTagEntity postTag){
        postTag.setPost(this);
        this.listOfPostTag.add(postTag);
    }
    

    我想这会解决你的问题

    【讨论】:

    • 这看起来很有希望,因为事实上我意识到我从未真正尝试将帖子设置在 postTag 端。但是我得到了这个错误: 原因:org.hibernate.PersistentObjectException:分离的实体传递给持久化:com.fakeexample.TagEntity。
    • PostTag 中的Tag 也被解析了,Hibernate 没有加载它所以也许这就是原因?
    • 你在使用 CascadeType.ALL 吗??
    • 您可以在这里获取更多信息stackoverflow.com/q/6378526/13914979
    • 再次您好,抱歉回复晚了。所以,是的,我正在使用 CascadeType.All。我尝试只使用合并和删除,但它也没有工作。当我使用 cascadeType.All 时,它说我的标签是分离的。当我使用 cascadeType.Merge 时,它​​会插入帖子,但不会插入任何帖子标签。但是,它似乎在更新期间运行良好。我认为如果设置了 id,persist 将不起作用,在这种情况下我应该使用 merge。当我坚持一个帖子时,它可能不会级联合并。问题是我没有选择保留或合并帖子。
    猜你喜欢
    • 2019-10-16
    • 2017-12-23
    • 2011-07-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多