【问题标题】:Hibernate: Saving a link table where one side has a unique constraint休眠:保存一侧具有唯一约束的链接表
【发布时间】:2018-11-29 21:31:09
【问题描述】:

我有以下架构(缩写)

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 使用现有的属性记录(如果存在),否则创建一个新记录并使用它。有没有办法用注释来配置它?如果没有,我是否必须查找属性实体,如果没有找到则只创建一个新的?

【问题讨论】:

  • 我不知道怎么做。您必须加载Comment 及其属性,并查看要添加的属性是否已存在于属性集中。如果是,什么都不做,如果不是,添加它。您还需要先查找Attribute,看看是否已经存在这样的属性。我不知道 JPA 有什么地方可以为你做这一切。另外,为什么连接表需要一个 id?为什么不在 Comment 实体上使用 ManyToMany 注释?

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


【解决方案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
}

然后第二个插入什么也不做,因为属性已经存在于集合中,由检查 id 的equals 方法标识。您需要做的是获取当前属性集以及现有评论。

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

【讨论】:

  • 我更喜欢在阅读了一些最佳实践之后手动映射连接表,因为如果连接表需要任何元数据,则很难更改代码以支持它。那么除了从数据库中选择然后添加到新关系之外,没有别的办法了吗?奇怪的是,没有内置的 hibernate 习惯用法。
  • 好吧,我想为什么现在编写不需要的代码,但没关系。检查属性是否存在更多的是应用程序逻辑,因此据我所知,将其放入 JPA 是没有意义的。
  • 我在填充链接表之前重组了代码以查找/创建属性,并从中删除了级联。还不错,可惜我不能使用很多数据库支持的 upsert 类型功能。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-19
  • 2014-01-12
  • 1970-01-01
  • 2012-06-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多