OneToMany关系,其中“许多”端可以与多个实体连接

如何解决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_typepost,则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_idresource_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要求您自己填充resourceIdresourceType。如果您想将PostTag视为单独的聚合根,则此方法很有意义,否则,它非常麻烦且容易出错,因为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_idPRIMARY KEY(id)

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-