Hibernate:保存 link table 其中一侧具有唯一约束
Hibernate: Saving a link table where one side has a unique constraint
我有以下架构(缩写)
Comment id, content, createdBy
Attribute id, key, value (unique constraint on key, value)
CommentAttribute id, comment_id, attribute_id
所以这是一个相当简单的模式。
我已经用最简单的 Comment 和 Attribute 实体映射了它,所以我不会 post 这里的代码。
Comment属性如下
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,所以我想对上面的内容进行建模,以便可以轻松地将记录添加到 link table。我有一个测试如下:
@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 时,我在属性 table 上遇到了唯一约束冲突,这并不奇怪,因为 Hibernate 正在尝试插入新记录。
我希望 Hibernate 使用现有的属性记录(如果存在),否则创建一个新记录并使用它。有没有办法用注释配置它?如果没有,是不是要查找属性实体,找不到才新建一个?
考虑一下,如果首先获取评论的属性并将其留给属性进行自我识别,那么 JPA 会做正确的事情。此外,您不需要手动创建连接 table,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();
我有以下架构(缩写)
Comment id, content, createdBy
Attribute id, key, value (unique constraint on key, value)
CommentAttribute id, comment_id, attribute_id
所以这是一个相当简单的模式。
我已经用最简单的 Comment 和 Attribute 实体映射了它,所以我不会 post 这里的代码。
Comment属性如下
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,所以我想对上面的内容进行建模,以便可以轻松地将记录添加到 link table。我有一个测试如下:
@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 时,我在属性 table 上遇到了唯一约束冲突,这并不奇怪,因为 Hibernate 正在尝试插入新记录。
我希望 Hibernate 使用现有的属性记录(如果存在),否则创建一个新记录并使用它。有没有办法用注释配置它?如果没有,是不是要查找属性实体,找不到才新建一个?
考虑一下,如果首先获取评论的属性并将其留给属性进行自我识别,那么 JPA 会做正确的事情。此外,您不需要手动创建连接 table,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();