Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/hibernate/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Hibernate “多”方可以与多个实体连接的一对多关系_Hibernate_Jpa - Fatal编程技术网

Hibernate “多”方可以与多个实体连接的一对多关系

Hibernate “多”方可以与多个实体连接的一对多关系,hibernate,jpa,Hibernate,Jpa,我想要一个能够将标记应用于各种实体的标记表。在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 w

我想要一个能够将标记应用于各种实体的标记表。在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'))
);
@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<>();
}
如果resource_type是post,那么resource_id将连接到post表的id字段,同样也连接到Story。namespace_id字段存在是因为虽然允许两个帖子具有相同的标记字符串,但所有my实体都被分组到名称空间中,并且同一名称空间中的两个实体不能具有相同的标记。希望这无关紧要

我不确定这些实体应该是什么样子。我试过这样的方法:

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'))
);
@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<>();
}
我肯定那是不对的,我甚至不确定有没有办法做到这一点。在标记实体方面,我没有@ManyToOne,因为它与各种不同的实体连接。

我知道您想要一个表示多个不同实体标记的标记表,而不是一个标记表+特定实体类型post\u标记、story\u标记等的连接表。,这就是JPA默认情况下将单向一对多映射的方式

那样的话,我相信这就是你要找的

基本上有三种方法可以解决这个问题:

1. @哪里有+@任何 使用@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.resource属性,该属性与故事和所有其他“可标记”实体相同

请注意,您可能希望添加一个基类/标记接口,如Taggable,并使用它而不是Object来限制可以分配给Tag.resource属性的类型。它应该可以工作,但我还没有测试过,所以我不是100%确定

2. @其中+标记中的显式联接列映射 对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;
}
现在,创建新标记需要您自己填充resourceId和resourceType。如果您想将Post和Tag作为单独的聚合根来处理,那么这种方法非常有意义,否则它非常麻烦且容易出错,因为Hibernate不能帮助您确保一致性,您需要自己管理它

3.继承+mappedBy 使用单一继承策略为帖子标签、故事标签等创建单独的实体,并将资源类型列视为鉴别器值:

@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:

添加一个新标签也很简单想要一个新的post标签吗?创建一个PostTag对象。想要一个新的故事标签吗?改为创建一个StoryTag对象。此外,如果您希望使用Post.Tags关联(即单向一对多)切换到管理标记,则此方法将是最容易转换的方法

请注意,在这种情况下,当然不能依靠Hibernate生成模式,因为它将尝试在指向所有候选表的resource_id列上创建FK约束

我创建了一个将所有三种方法表示为单独提交的应用程序。对于每种方法,都有一个测试来证明它确实有效。请注意,这三种方案的数据库结构都是相同的

作为旁注,我现在才注意到表定义中的主键资源类型、名称空间id和标记部分,所以我必须问:您知道这个问题是在考虑一对多关联的情况下提出和回答的,而不是多对多关联,对吗

我这样问是因为有了这样一个PK定义,最多一个POST可以有一个标签,标签列的值是给定的——当然,对于给定的名称空间_id。我假设这是一个输入错误,您真正想要的是一个主键id加上UNIQUEresource\u type、resource\u id、namespace\u id、tag

我知道您想要一个代表多个不同实体的标记的标记表,而不是一个针对特定实体类型post\u标记、story\u标记等的标记表+联接表。,这就是JPA默认情况下将单向一对多映射的方式

那样的话,我相信这就是你要找的

基本上有三种方法可以解决这个问题:

1. @哪里有+@任何 使用@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.resource属性,该属性与故事和所有其他“可标记”实体相同

请注意,您可能希望添加一个基类/标记接口,如Taggable,并使用它而不是Object来限制可以分配给Tag.resource属性的类型。它应该可以工作,但我还没有测试过,所以我不是100%确定

2. @其中+标记中的显式联接列映射 对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;
}
现在,创建新标记需要您自己填充resourceId和resourceType。如果您想处理Po,这种方法非常有意义 st和Tag作为单独的聚合根,否则会非常麻烦且容易出错,因为Hibernate不能帮助您确保一致性,您需要自己管理它

3.继承+mappedBy 使用单一继承策略为帖子标签、故事标签等创建单独的实体,并将资源类型列视为鉴别器值:

@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:

添加一个新标签也很简单想要一个新的post标签吗?创建一个PostTag对象。想要一个新的故事标签吗?改为创建一个StoryTag对象。此外,如果您希望使用Post.Tags关联(即单向一对多)切换到管理标记,则此方法将是最容易转换的方法

请注意,在这种情况下,当然不能依靠Hibernate生成模式,因为它将尝试在指向所有候选表的resource_id列上创建FK约束

我创建了一个将所有三种方法表示为单独提交的应用程序。对于每种方法,都有一个测试来证明它确实有效。请注意,这三种方案的数据库结构都是相同的

作为旁注,我现在才注意到表定义中的主键资源类型、名称空间id和标记部分,所以我必须问:您知道这个问题是在考虑一对多关联的情况下提出和回答的,而不是多对多关联,对吗


我这样问是因为有了这样一个PK定义,最多一个POST可以有一个标签,标签列的值是给定的——当然,对于给定的名称空间_id。我假设这是一个输入错误,您真正想要的是一个主键id加上UNIQUEresource\u类型、资源\u id、名称空间\u id、标记

谢谢!我假设它应该是@Whereclause=resource\u type='blueprint'?我试过了,当我查询我的帖子时,没有一个帖子还有标签。标记实体应该是什么样子?我没有在那里放置任何与连接相关的注释。当然,它有一个@Columnname=resource\u id,有一点可能是错误的,java字段名是camelcase,但这些注释中的子句和名称字段使用下划线。但现在我两个都试过了,所以可能不是这样。哈,这很奇怪,应该是开箱即用的。您甚至不需要@Columnname=resource\u id,因为Hibernate将使用@JoinColumn中的列定义。在任何情况下,我强烈建议@ManyToAny而不是@Where,因为保存实体时可能会出现问题。另外,关于camelcase vs下划线,子句和名称使用物理列名,而不是Java字段名@Where正好表示在获取childrenFYI时将附加到本机SELECT查询的字符串,Post上的@JoinColumn还需要foreignKey=@ForeignKeyvalue=ConstraintMode.NO_约束,以防止标记生成外键约束,从而阻止它与多个实体连接。谢谢!我假设它应该是@Whereclause=resource\u type='blueprint'?我试过了,当我查询我的帖子时,没有一个帖子还有标签。标记实体应该是什么样子?我没有在那里放置任何与连接相关的注释。当然,它有一个@Columnname=resource\u id,有一点可能是错误的,java字段名是camelcase,但这些注释中的子句和名称字段使用下划线。但现在我两个都试过了,所以可能不是这样。哈,这很奇怪,应该是开箱即用的。您甚至不需要@Columnname=resource\u id,因为Hibernate将使用@JoinColumn中的列定义。在任何情况下,我强烈建议@ManyToAny而不是@Where,因为保存实体时可能会出现问题。另外,关于camelcase vs下划线,子句和名称使用物理列名,而不是Java字段名@Where正好表示在获取childrenFYI时将附加到本机SELECT查询的字符串,Post上的@JoinColumn还需要foreignKey=@ForeignKeyvalue=ConstraintMode.NO_约束,以防止标记生成外键约束,从而阻止它与多个实体连接。