在对象中休眠 link 到下一个 and/or 上一个
Hibernate link to next and/or previous within an object
我遇到以下情况,我没有成功地为它编写正确的 hibernate 注释。
情况
假设一位企业主拥有 2 家商店。在这些 Stores 中,他销售 Articles。现在,当用户进入商店时,他们希望对购物清单上的商品(不重要 class)进行排序,以便按照出现的顺序进行排序。企业主对每个商店的每篇文章都有一个概览,link 到 previousArticle 和 nextArticle。使用这个列表,我们可以很容易地“计算”出顺序。所以对于一家商店来说,这实际上不是问题。问题是每当第二家商店进来时。
代码:
我删除了一些 lombok 注释和其他样板代码以使示例尽可能简洁。
@Entity
@Table(name = STORES)
public final class StoreSequence {
@EmbeddedId
@AttributeOverride(name = "value", column = @Column(name = STORE_NUMBER))
private StoreNumber storeNumber;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = STORE_NUMBER, referencedColumnName= STORE_NUMBER, nullable = false)
private Set<Article> articles;
}
@Entity
@Table(name = ARTICLES)
class Article {
//NOTICE NO STORENUMBER WAS ADDED EARLIER SINCE ONLY 1 STORE WAS THERE.
@EmbeddedId
@Column(name = ARTICLE_ID)
private ArticleNumber id;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = PREVIOUS_ARTICLE_ID, referencedColumnName = ARTICLE_ID)
@Setter // to move things around
private Article previous;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = NEXT_ARTICLE_ID, referencedColumnName = ARTICLE_ID)
@Setter // to move things around
private Article next;
}
这行得通...但是,如果我现在“启动”第二家商店 (B)(当然它与商店 (A) 有一些相同的 articleId,但顺序不同),例如,我无法创建商店 B 列表中的一篇文章,如果它具有相同的 ID。 (UniqueConstraintViolationException).
所以可能,我还必须将商店编号添加到文章中 class(从那时起,一篇文章就可以知道它在哪个商店的哪个位置...有意义)但是...
问题
如果我在文章 class 中包含 storenumber 作为 id 的一部分,我该如何在下一个和上一个字段上进行映射,因为他们现在还需要在他们的 PK 中包含 storeNumber 而不添加数据库列 next_storenumber 和 previous_storenumber(将有相同的商店编号 -> 业务规则)并保持字段可更新。
可更新很重要:我可以在商店里移动东西。
我成功阅读了使用添加的字段:
@JoinColumns({
@JoinColumn(name = PREVIOUS_ARTICLE, referencedColumnName = NEXT_ARTICLE, insertable = false, updatable = false),
@JoinColumn(name = STORE_NUMBER, referencedColumnName = STORE_NUMBER, insertable = false, updatable = false),
})
但是当省略 insertable = false 和 updateable = false 时,我得到一个启动异常,说明我引用了 STORE_NUMBER table 并告诉我只允许使用 insertable 和可更新的错误。
编辑:
一种选择是将商店编号列复制到 next_store_number 和 previous_store_number 列,但我可以将一篇文章放在另一家商店的文章旁边
指出真正阅读了所有这些内容的人!
奖金指向找到解决方案的人!
Game/Set/Match 感谢在文章 class 中提出无需添加商店编号即可工作的解决方案的人!出于好奇想知道是否可能
全部,
经过问题分析,我们看到了两个方案:
- 添加 surrogate/artificial 密钥(自动生成)并通过此号码存储 link。由于我们数据库的性质,它会导致带有文章编号的列、带有指向下一篇文章编号的数字的列等。maintenance/db 观点不理想。
- 添加 2 个附加列以正确存储 next_store_id 和 previous_store_id 到 link 字段。但是,然后我可以将一篇文章放在另一家商店的文章旁边,并且我有额外的列和日期副本(将对它们施加约束以使其相同或为空)
幸运的是我偶然发现了第三种解决方案:
只需为 nextId 和 previousId 添加一个额外的 @Column 注释字段。实际上是一个干净的“Hack”
@Entity
@Table(name = STORES)
public final class StoreSequence {
@EmbeddedId
@Column(name = STORE_NUMBER)
private Long storeNumber;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = STORE_NUMBER, referencedColumnName= STORE_NUMBER, nullable = false, insertable = false, updatable = false)
private Set<Article> articles;
}
@Entity
@Table(name = ARTICLES)
class Article {
@EmbeddedId
private ArticleInStore id; //CONTAINING STORE AND ARTICLE_ID
@OneToOne(cascade = CascadeType.ALL)
@JoinColumns({
@JoinColumn(name = PREVIOUS_ARTICLE_ID, referencedColumnName = ARTICLE_ID, insertable = false, updatable = false),
@JoinColumn(name = STORE_ID, referencedColumnName = STORE_ID, insertable = false, updatable = false)
})
//NOTICE THE SETTER IS CUSTOM NOW
//CANNOT MAKE WRITABLE SINCE THEN WE NEED PREVIOUS_STORE_ID COLUMN.
private Article previous;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumns({
@JoinColumn(name = NEXT_ARTICLE_ID, referencedColumnName = ARTICLE_ID, insertable = false, updatable = false),
@JoinColumn(name = STORE_ID, referencedColumnName = STORE_ID, insertable = false, updatable = false)
})
@JoinColumn(name = NEXT_ARTICLE_ID, referencedColumnName = ARTICLE_ID)
//NOTICE THE SETTER IS CUSTOM NOW
//CANNOT MAKE WRITABLE SINCE THEN WE NEED NEXT_STORE_ID COLUMN.
private Article next;
/***********/
/* THE FIX */
/***********/
//ADDED FIELD
@Column(name = NEXT_ARTICLE_ID)
private Long nextArticleId
//ADDED FIELD
@Column(name = PREVIOUS_ARTICLE_ID)
private Long previousArticleId
//CUSTOM SETTER
public void setNextArticle(Article nextArticle) {
this.next = nextArticle;
this.nextArticleId = nextArticle.getId().getArticleId();
}
//CUSTOM SETTER
public void setPreviousArticle(Article previousArticle) {
this.previous = previousArticle;
this.previousArticleId = previousArticle.getId().getArticleId();
}
}
结论
我们使用 id 字段在数据库上设置 articleId link,以及要在代码中使用的对象字段(如前所述,但不是 insertable/updateable)。 Insertable/updatable = false 不会将这些字段添加到 update/insert 的结果查询中,但会为 select 添加这些字段。添加列会将那个字段添加到查询中。每当更新下一个字段时正确设置 id 将确保它们被写入数据库。
备注
我们也非常(高兴地)惊讶地看到查询针对 @OneToOne 进行了优化,没有 n+x select 问题。我们解决了通过自定义 getter/setter 和 @Access(Access.Property) 填充临时自定义 SortingCapable 列表对象来填充 StoreSequence 中的文章列表的问题。所以基本上是 LinkedList 的概念,但可以持久保存到具有相当好的查询性能的数据库,它对持久集进行排序(通过引用)。因此,当在两篇文章之间添加一篇文章时,只会为周围的文章发送更新,而不是为列表中的每篇文章发送更新。
我遇到以下情况,我没有成功地为它编写正确的 hibernate 注释。
情况
假设一位企业主拥有 2 家商店。在这些 Stores 中,他销售 Articles。现在,当用户进入商店时,他们希望对购物清单上的商品(不重要 class)进行排序,以便按照出现的顺序进行排序。企业主对每个商店的每篇文章都有一个概览,link 到 previousArticle 和 nextArticle。使用这个列表,我们可以很容易地“计算”出顺序。所以对于一家商店来说,这实际上不是问题。问题是每当第二家商店进来时。
代码:
我删除了一些 lombok 注释和其他样板代码以使示例尽可能简洁。
@Entity
@Table(name = STORES)
public final class StoreSequence {
@EmbeddedId
@AttributeOverride(name = "value", column = @Column(name = STORE_NUMBER))
private StoreNumber storeNumber;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = STORE_NUMBER, referencedColumnName= STORE_NUMBER, nullable = false)
private Set<Article> articles;
}
@Entity
@Table(name = ARTICLES)
class Article {
//NOTICE NO STORENUMBER WAS ADDED EARLIER SINCE ONLY 1 STORE WAS THERE.
@EmbeddedId
@Column(name = ARTICLE_ID)
private ArticleNumber id;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = PREVIOUS_ARTICLE_ID, referencedColumnName = ARTICLE_ID)
@Setter // to move things around
private Article previous;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = NEXT_ARTICLE_ID, referencedColumnName = ARTICLE_ID)
@Setter // to move things around
private Article next;
}
这行得通...但是,如果我现在“启动”第二家商店 (B)(当然它与商店 (A) 有一些相同的 articleId,但顺序不同),例如,我无法创建商店 B 列表中的一篇文章,如果它具有相同的 ID。 (UniqueConstraintViolationException).
所以可能,我还必须将商店编号添加到文章中 class(从那时起,一篇文章就可以知道它在哪个商店的哪个位置...有意义)但是...
问题
如果我在文章 class 中包含 storenumber 作为 id 的一部分,我该如何在下一个和上一个字段上进行映射,因为他们现在还需要在他们的 PK 中包含 storeNumber 而不添加数据库列 next_storenumber 和 previous_storenumber(将有相同的商店编号 -> 业务规则)并保持字段可更新。 可更新很重要:我可以在商店里移动东西。 我成功阅读了使用添加的字段:
@JoinColumns({
@JoinColumn(name = PREVIOUS_ARTICLE, referencedColumnName = NEXT_ARTICLE, insertable = false, updatable = false),
@JoinColumn(name = STORE_NUMBER, referencedColumnName = STORE_NUMBER, insertable = false, updatable = false),
})
但是当省略 insertable = false 和 updateable = false 时,我得到一个启动异常,说明我引用了 STORE_NUMBER table 并告诉我只允许使用 insertable 和可更新的错误。
编辑: 一种选择是将商店编号列复制到 next_store_number 和 previous_store_number 列,但我可以将一篇文章放在另一家商店的文章旁边
指出真正阅读了所有这些内容的人!
奖金指向找到解决方案的人!
Game/Set/Match 感谢在文章 class 中提出无需添加商店编号即可工作的解决方案的人!出于好奇想知道是否可能
全部,
经过问题分析,我们看到了两个方案:
- 添加 surrogate/artificial 密钥(自动生成)并通过此号码存储 link。由于我们数据库的性质,它会导致带有文章编号的列、带有指向下一篇文章编号的数字的列等。maintenance/db 观点不理想。
- 添加 2 个附加列以正确存储 next_store_id 和 previous_store_id 到 link 字段。但是,然后我可以将一篇文章放在另一家商店的文章旁边,并且我有额外的列和日期副本(将对它们施加约束以使其相同或为空)
幸运的是我偶然发现了第三种解决方案: 只需为 nextId 和 previousId 添加一个额外的 @Column 注释字段。实际上是一个干净的“Hack”
@Entity
@Table(name = STORES)
public final class StoreSequence {
@EmbeddedId
@Column(name = STORE_NUMBER)
private Long storeNumber;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = STORE_NUMBER, referencedColumnName= STORE_NUMBER, nullable = false, insertable = false, updatable = false)
private Set<Article> articles;
}
@Entity
@Table(name = ARTICLES)
class Article {
@EmbeddedId
private ArticleInStore id; //CONTAINING STORE AND ARTICLE_ID
@OneToOne(cascade = CascadeType.ALL)
@JoinColumns({
@JoinColumn(name = PREVIOUS_ARTICLE_ID, referencedColumnName = ARTICLE_ID, insertable = false, updatable = false),
@JoinColumn(name = STORE_ID, referencedColumnName = STORE_ID, insertable = false, updatable = false)
})
//NOTICE THE SETTER IS CUSTOM NOW
//CANNOT MAKE WRITABLE SINCE THEN WE NEED PREVIOUS_STORE_ID COLUMN.
private Article previous;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumns({
@JoinColumn(name = NEXT_ARTICLE_ID, referencedColumnName = ARTICLE_ID, insertable = false, updatable = false),
@JoinColumn(name = STORE_ID, referencedColumnName = STORE_ID, insertable = false, updatable = false)
})
@JoinColumn(name = NEXT_ARTICLE_ID, referencedColumnName = ARTICLE_ID)
//NOTICE THE SETTER IS CUSTOM NOW
//CANNOT MAKE WRITABLE SINCE THEN WE NEED NEXT_STORE_ID COLUMN.
private Article next;
/***********/
/* THE FIX */
/***********/
//ADDED FIELD
@Column(name = NEXT_ARTICLE_ID)
private Long nextArticleId
//ADDED FIELD
@Column(name = PREVIOUS_ARTICLE_ID)
private Long previousArticleId
//CUSTOM SETTER
public void setNextArticle(Article nextArticle) {
this.next = nextArticle;
this.nextArticleId = nextArticle.getId().getArticleId();
}
//CUSTOM SETTER
public void setPreviousArticle(Article previousArticle) {
this.previous = previousArticle;
this.previousArticleId = previousArticle.getId().getArticleId();
}
}
结论
我们使用 id 字段在数据库上设置 articleId link,以及要在代码中使用的对象字段(如前所述,但不是 insertable/updateable)。 Insertable/updatable = false 不会将这些字段添加到 update/insert 的结果查询中,但会为 select 添加这些字段。添加列会将那个字段添加到查询中。每当更新下一个字段时正确设置 id 将确保它们被写入数据库。
备注
我们也非常(高兴地)惊讶地看到查询针对 @OneToOne 进行了优化,没有 n+x select 问题。我们解决了通过自定义 getter/setter 和 @Access(Access.Property) 填充临时自定义 SortingCapable 列表对象来填充 StoreSequence 中的文章列表的问题。所以基本上是 LinkedList 的概念,但可以持久保存到具有相当好的查询性能的数据库,它对持久集进行排序(通过引用)。因此,当在两篇文章之间添加一篇文章时,只会为周围的文章发送更新,而不是为列表中的每篇文章发送更新。