简体   繁体   English

休眠-持久化实体时的ConstraintViolation

[英]Hibernate - ConstraintViolation when persisting entity

I have an entity that contains a relationship to another entity in a manner I've never had to encounter before, and I'm getting an exception: "org.hibernate.exception.ConstraintViolationException: could not execute statement". 我有一个实体,该实体以从未有过的方式包含与另一个实体的关系,并且遇到了一个异常:“ org.hibernate.exception.ConstraintViolationException:无法执行语句”。

The parent entity is called "Post". 父实体称为“发布”。 A post can contain several Keyword entities. 帖子可以包含多个关键字实体。 Keyword entities are unique by value, that is, if two posts contain the same keyword, both posts reference the same keyword entity. 关键字实体按值是唯一的,也就是说,如果两个帖子包含相同的关键字,则两个帖子都引用相同的关键字实体。

My thought process was that there are many posts, each referencing many keywords, and any one keyword can be referenced by multiple posts, so it should be an @ManyToMany relationship. 我的想法是,有很多帖子,每个帖子都引用了许多关键字,并且任何一个关键字都可以被多个帖子引用,因此应该是@ManyToMany关系。 Obviously, it's not working. 显然,它不起作用。 Inspecting the database shows that it is successfully persisting a few posts before it starts failing. 检查数据库表明,该数据库在开始失败之前已成功保留了一些帖子。 As long as all the keywords are unique, it seems to be fine, but I'm thinking that it is dying whenever it's trying to persist a post with a keyword that is already being referenced by another post. 只要所有关键字都是唯一的,这似乎很好,但是我认为只要尝试使用已被另一篇文章引用的关键字来保留一篇文章,它就会死掉。 Not sure how to fix this. 不确定如何解决此问题。

Here is what the classes look like (short version): 这些类(简短版本)如下所示:

Post: 发布:

@Entity
public class Post implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "post_id_seq")
    @SequenceGenerator(name = "post_id_seq", sequenceName = "post_id_seq", allocationSize = 1)
    private Long id;

    @ElementCollection(fetch = FetchType.EAGER)
    @ManyToMany(cascade = CascadeType.ALL)
    private Set<Keyword> keywords = new HashSet<>();
}

Keyword: 关键词:

@Entity
public class Keyword implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "keyword_id_seq")
    @SequenceGenerator(name = "keyword_id_seq", sequenceName = "keyword_id_seq", allocationSize = 1)
    private Long id;

    @Column(name = "KEYWORD_VALUE")
    private String value;
    private int count = 1;
}

UPDATE: 更新:

Here is the code I use in my service class to add a keyword to a post. 这是我在服务类中用于向帖子添加关键字的代码。 Basically I have a Post object already that has Keywords filled in (request comes in via AJAX from a web front end and Spring unmarshals it automatically to a Post object). 基本上,我已经有一个已填充关键字的Post对象(请求是通过AJAX从Web前端输入的,Spring会自动将其解组到Post对象)。 I have to loop through each keyword and see if an entity with the same value already exists in persistence. 我必须遍历每个关键字,看看持久性中是否已经存在具有相同值的实体。 If so, increment the count for that keyword, merge it, then add that entity to the set that will end up replacing the Set that came in the request. 如果是这样,请增加该关键字的计数,将其合并,然后将该实体添加到将最终替换请求中包含的Set的set中。 If it doesn't already exist, I just use the Keyword that came in the request. 如果尚不存在,则只使用请求中附带的关键字。 Previously, I wasn't saving/merging the Keywords independently before adding them to the Post and persisting the post, but I started getting errors stating: 以前,在将关键字添加到帖子中并保留帖子之前,我并没有独立保存/合并关键字,但是我开始出现错误,指出:

org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.saic.jswe.clients.swtc.domain.social.Keyword org.hibernate.TransientObjectException:对象引用了一个未保存的临时实例-在刷新之前保存该临时实例:com.saic.jswe.clients.swtc.domain.social.Keyword

Anyway, here is my service code: 无论如何,这是我的服务代码:

public void addPost(Post post){
Set<Keyword> keywords = new HashSet<>();
        for (Keyword keyword : post.getKeywords()) {
            Keyword persistedKeyword = keywordDao.findByValue(keyword.getValue());
            if (persistedKeyword != null) {
                persistedKeyword.setCount(persistedKeyword.getCount() + 1);
                keywordDao.merge(persistedKeyword);
                keywords.add(persistedKeyword);

            } else {
                keywordDao.persist(keyword);
                keywords.add(keyword);
            }
        }
        post.setKeywords(keywords);
postDao.persist(post);
}

Also, during my testing when I'm getting this error, it's just a single thread attempting to add test Post objects one at a time. 同样,在测试中,当我遇到此错误时,它只是尝试一次添加一个测试Post对象的单个线程。

Checking the logs, here is the actual constraint violation: 检查日志,这是实际的约束冲突:

rg.postgresql.util.PSQLException: ERROR: insert or update on table "keyword" violates foreign key constraint "fk_3tcnkw7v196mudsgmy3nriibl" Detail: Key (id)=(1) is not present in table "post". rg.postgresql.util.PSQLException:错误:在表“关键字”上插入或更新违反了外键约束“ fk_3tcnkw7v196mudsgmy3nriibl”。详细信息:表(post)中不存在键(id)=(1)。

Hmmm... per the above code, it should only be adding a reference to a Keyword object with an ID if it did in fact find it in persistence. 嗯...按照上面的代码,如果它确实在持久性中找到了它,则应该只添加对具有ID的Keyword对象的引用。 The keyword objects coming in with the Post object via the request should all have null IDs as they're not yet persisted. 通过请求与Post对象一起出现的关键字对象应该都具有空ID,因为它们尚未持久化。

I found where the issue came into play. 我发现了问题所在。 A join table was being created called "post_keywords". 正在创建一个称为“ post_keywords”的联接表。 It had 2 columns, one called "post" and one called "keyword". 它有2列,一列称为“帖子”,一列称为“关键字”。 Each row represented the ID of a post and the ID of a keyword contained in that post. 每行代表一个帖子的ID和该帖子中包含的关键字的ID。 If there were multiple keywords in a post, there could be duplicate entries in the post column. 如果帖子中有多个关键字,则帖子列中可能有重复的条目。 However, as soon as a different post entity attempted to reference a keyword that was already used, it would complain about that ID already being present. 但是,一旦其他邮政实体尝试引用已经使用的关键字,它就会抱怨该ID已经存在。 Here's the visual: 这是视觉效果:

post | keyword
-----+--------
 1   |   1
 1   |   2
 1   |   4
 2   |   3
 2   |   4   <--- this would be a problem since keyword 4 is already related to post 1

So my knowledge/understanding of JPA is pretty weak, but I've only ever needed real basic relationships. 因此,我对JPA的了解/了解非常薄弱,但是我只需要真正的基本关系。 Given that I understood where the problem was happening, I decided to quit playing and experimenting and start reading. 鉴于我了解问题的根源,我决定退出游戏和实验,开始阅读。

For a minute, I thought I found a solution just using a OneToMany relationship, because I didn't necessarily care or need the keyword entity to directly know which posts reference it. 一分钟,我以为我发现了一个仅使用OneToMany关系的解决方案,因为我不一定在乎或不需要关键字实体直接知道哪些帖子引用了它。 This was incorrect, however. 但是,这是不正确的。 I could get that code to execute without error, but I ended up with each keyword only being owned by one entity. 我可以使该代码无错误地执行,但最终我得到的每个关键字仅归一个实体所有。 As each post tried to reference that keyword, it would just override the previous ownership of the keyword. 当每个帖子都尝试引用该关键字时,它将覆盖该关键字的先前所有权。 Anyway, I really did need a ManyToMany relationship. 无论如何,我确实确实需要一个ManyToMany关系。

I ended up finding examples ( http://en.wikibooks.org/wiki/Java_Persistence/ManyToMany ) showing tables where the multiple child entities reference the same parent entity, so I just implemented the same JPA attributes in my code and viola, it worked. 我最终找到了示例( http://en.wikibooks.org/wiki/Java_Persistence/ManyToMany ),其中显示了多个子实体引用相同父实体的表,因此我在代码和中提琴中实现了相同的JPA属性,工作了。 Here is what the code looks like now: 这是现在的代码:

@Entity
public class Post implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "post_id_seq")
    @SequenceGenerator(name = "post_id_seq", sequenceName = "post_id_seq", allocationSize = 1)
    @Column(name="POST_ID")
    private Long id;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
      name="POST_KEYWORD",
      joinColumns={@JoinColumn(name="POST_ID", referencedColumnName="POST_ID")},
      inverseJoinColumns={@JoinColumn(name="KEYWORD_ID", referencedColumnName="ID")})
    private Set<Keyword> keywords = new HashSet<>();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM