如何解决OneToMany关系,其中“许多”端可以与多个实体连接
我想要一个Tag
表,该表能够将标签应用于各种实体。在SQL中,它看起来像这样:
CREATE TABLE tag (
id number GENERATED ALWAYS AS IDENTITY NOT NULL,resource_type varchar2(64) NOT NULL,resource_id varchar2(256),namespace_id varchar2(256),tag varchar2(128),time_created timestamp with time zone NOT NULL,PRIMARY KEY (resource_type,namespace_id,tag),CHECK (resource_type in ('post','story'))
);
如果resource_type
为post
,则resource_id
旨在加入Post
表的id
字段(对于Story
同样)。 (之所以使用namespace_id
字段是因为虽然允许两个Posts
具有相同的标记字符串,但是我所有的实体都被分组到命名空间中,并且同一命名空间中的两个实体不能具有相同的标记。希望这是不相关的)
我不确定这些实体的外观。我尝试过这样的事情:
@Entity
@Table(name = "post")
public class Post {
@Id
private String id;
...
@NonNull
@Default
@OneToMany(fetch = FetchType.EAGER,targetEntity=Tag.class)
// @JoinColumn(name = "resource_id")
@Where(clause = "resource_id=post.id and resource_type='post'")
@ElementCollection
private List<Tag> tags = new ArrayList<>();
}
我确定这是不对的,而且我不确定是否有办法做到这一点。在Tag
实体方面,我没有@ManyToOne
,因为它与各种不同的实体连接。
解决方法
我了解您想要一个tag
表来表示多个不同实体的标签,而不是一个tag
表+特定实体类型(post_tags
的联接表, story_tags
等),这是JPA默认情况下将单向一对多映射的方式。
在这种情况下,我相信您正在寻找this。
基本上有三种方法可以解决此问题:
1。 @Where + @Any
使用@Where
限制Post.tags
集合中的匹配实体:
@Entity public class Post {
@Id
private String id;
@OneToMany
@Immutable
@JoinColumn(name = "resource_id",referencedColumnName = "id",insertable = false,updatable = false)
@Where(clause = "resource_type = 'post'")
private Collection<Tag> tags;
}
然后,在Tag
中使用@Any
定义多目标关联:
@Entity public class Tag {
@Id
private Long id;
private String tag;
@CreationTimestamp
private Instant timeCreated;
@JoinColumn(name = "resource_id")
@Any(metaColumn = @Column(name = "resource_type"),optional = false,fetch = LAZY)
@AnyMetaDef(idType = "string",metaType = "string",metaValues = {
@MetaValue(value = "post",targetEntity = Post.class),@MetaValue(value = "story",targetEntity = Story.class),})
private Object resource;
}
将新的Tag
添加到Post
很简单,只需将Post
分配给Tag.resource
属性(对于故事和所有其他“可标记”实体相同)
(请注意,您可能想添加诸如Taggable
之类的基类/标记接口,并使用它而不是Object
来限制可能分配给Tag.resource
属性的类型。它应该可以,但是我还没有测试过,所以我不确定100%)
2。 @Where +标记中的显式联接列映射
对Post
使用与以前相同的方法,并将resource_id
和resource_type
列映射为显式属性:
@Entity public class Tag {
@Id
private Long id;
private String tag;
@CreationTimestamp
private Instant timeCreated;
@Column(name = "resource_id")
private String resourceId;
private String resourceType;
}
现在创建新的Tag
要求您自己填充resourceId
和resourceType
。如果您想将Post
和Tag
视为单独的聚合根,则此方法很有意义,否则,它非常麻烦且容易出错,因为Hibernate无法帮助您确保一致性,因此需要管理它自己。
3。继承+ mappingBy
使用单一继承策略为帖子标签,故事标签等创建单独的实体,并将resource_type
列作为区分值:
@Entity
@Inheritance(strategy = SINGLE_TABLE)
@DiscriminatorColumn(name = "resource_type")
public abstract class Tag {
@Id
private Long id;
private String tag;
@CreationTimestamp
private Instant timeCreated;
}
@Entity
@DiscriminatorValue("post")
public class PostTag extends Tag {
@JoinColumn(name = "resource_id")
@ManyToOne(optional = false,fetch = LAZY)
private Post post;
}
@Entity
@DiscriminatorValue("story")
public class StoryTag extends Tag {
@JoinColumn(name = "resource_id")
@ManyToOne(optional = false,fetch = LAZY)
private Story story;
}
此解决方案的优势在于,在“可标记”实体中,您不再需要拥有“伪造的” @OneToMany
关联,而可以使用mappedBy
:
@Entity public class Post {
@Id
private String id;
@OneToMany(mappedBy = "post")
private Collection<PostTag> tags;
}
@Entity public class Story {
@Id
private String id;
@OneToMany(mappedBy = "story")
private Collection<StoryTag> tags;
}
添加新的Tag
也得到了简化(是否要添加新的帖子标签?创建一个PostTag
对象。想要一个新的故事标签?而是创建一个StoryTag
对象)。另外,如果您想切换为使用Tag
关联(即单向一对多)来管理Post.tags
,则这种方法将是最容易转换的。
(请注意,在这种情况下,您当然不能依靠Hibernate来生成模式,因为它随后将尝试在指向所有候选表的resource_id
列上创建FK约束)
我创建了一个github repo,将所有三种方法表示为单独的提交。对于每种方法,都有一个测试证明它确实有效。请注意,这三种情况的数据库结构都是相同的。
(作为一个附带说明,我现在仅注意到表定义的PRIMARY KEY (resource_type,namespace_id,tag)
部分,因此我不得不问:您确实理解并询问了这个问题,并牢记了一对多关联,并且不是多对多,对吧?
我问,因为使用这样的PK定义,对于给定的{{1},最多post
可以具有tag
列的给定值的tag
}, 当然。我假设这是一个错字,而您真正想要的是namespace_id
和PRIMARY KEY(id)
)
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。