休眠:保存链接表,其中一侧具有唯一约束

时间:2018-06-20 16:59:55

标签: java hibernate spring-data-jpa hibernate-mapping

我有以下模式(缩写)

Comment id, content, createdBy
Attribute id, key, value (unique constraint on key, value)
CommentAttribute id, comment_id, attribute_id

所以这是一个相当简单的模式。

我已将其与Comment和Attribute实体的最简单实体进行映射,所以我不会在此处发布代码。

CommentAttribute如下

import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Entity
@Table(name = "comment_attributes")
public class CommentAttribute {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Cascade(value = {CascadeType.ALL})
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "comment_id", nullable = false)
    private Comment comment;

    @Cascade(value = {CascadeType.ALL})
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "attribute_id", nullable = false)
    private Attribute attribute;

    public Long getId() {
        return id;
    }

    public CommentAttribute setId(final Long id) {
        this.id = id;
        return this;
    }

    public Comment getComment() {
        return comment;
    }

    public CommentAttribute setComment(final Comment comment) {
        this.comment = comment;
        return this;
    }

    public Attribute getAttribute() {
        return attribute;
    }

    public CommentAttribute setAttribute(final Attribute attribute) {
        this.attribute = attribute;
        return this;
    }
}

目的是用户将添加具有一个或多个属性的评论。类似于下面的GraphQL缩写

addComment(content: "a comment", [{name: "threadId" value: "thread1"}])

我正在使用Spring JPA和Hibernate,因此我想对以上模型进行建模,以便轻松将记录添加到链接表中。我进行了如下测试:

@Test
public void whenAddingTwoCommentsWithSameAttributesThenNoDuplicateCreated() {
    Comment comment = new Comment();
    comment.setCreatedBy("user1");
    comment.setContent("some test comment");

    Attribute attribute = new Attribute();
    attribute.setKey("threadId");
    attribute.setValue("thread1");

    CommentAttribute commentAttribute = new CommentAttribute();
    commentAttribute.setComment(comment);
    commentAttribute.setAttribute(attribute);

    commentAttributeRepository.saveAndFlush(commentAttribute);

    Comment comment2 = new Comment();
    comment2.setCreatedBy("user1");
    comment2.setContent("some test comment2");

    Attribute attribute2 = new Attribute();
    attribute2.setKey("threadId");
    attribute2.setValue("thread1");
    attribute2.setTenantId("customer1");

    CommentAttribute commentAttribute2 = new CommentAttribute();
    commentAttribute2.setComment(comment2);
    commentAttribute2.setAttribute(attribute2);

    commentAttributeRepository.saveAndFlush(commentAttribute2);

    final List<CommentAttribute> all = commentAttributeRepository.findAll();
    assertThat(all).hasSize(2);
    assertThat(all.get(0).getComment().getContent()).isEqualTo("some test comment");
    assertThat(all.get(0).getAttribute().getValue()).isEqualTo("thread1");

    assertThat(all.get(1).getComment().getContent()).isEqualTo("some test comment2");
    assertThat(all.get(1).getAttribute().getValue()).isEqualTo("thread1");

}

因此attribute2变量实际上是唯一的。当保存commentAttribute2时,我在属性表上遇到了唯一的约束冲突,这并不奇怪,因为Hibernate试图插入一条新记录。

我希望Hibernate使用现有的属性记录(如果存在),否则创建一个新记录并使用它。有一些使用注释配置的方式吗?如果没有,我是否必须查找属性实体,并且仅在找不到一个新实体时创建一个新实体?

1 个答案:

答案 0 :(得分:1)

请考虑一下,如果首先获取注释的属性并将其留给属性进行自我识别,那么JPA会做正确的事情。另外,您不需要手动创建联接表,JPA也会为您完成。

@Entity
public class Comment {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;
    @ManyToMany
    private Set<Attribute> attributes;
    // getters, setters
}

@Entity
public class Attribute {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;
    // getters, setters, 
    // AND hashCode and equals using the id field
}

然后,第二个插入操作不执行任何操作,因为equals方法检查ID所标识的属性已经存在于集合中。您要做的就是获取当前属性集以及现有注释。

tx.begin();
Comment c = new Comment();
Attribute a = new Attribute();
em.persist(a);
c.setAttributes(new HashSet<>());
c.getAttributes().add(a);
em.persist(c);
tx.commit();

// to remove everything from cache
em.clear();

// this does nothing except a select since the attribute is already in the set of attributes
// and in fact the `em.find` does not issue a select in this case because
// the attribute gets loaded into the cache from the Comment select.
tx.begin();
Comment c2 = em.createQuery("select c from Comment c left join fetch c.attributes where c.id = 2", Comment.class).getSingleResult();
Attribute a2 = em.find(Attribute.class, 1);
c2.getAttributes().add(a2);
tx.commit();