Spring-Data-JPA ManyToMany 与额外列的关系
Posted
技术标签:
【中文标题】Spring-Data-JPA ManyToMany 与额外列的关系【英文标题】:Spring-Data-JPA ManyToMany relationship with extra column 【发布时间】:2019-03-09 22:34:51 【问题描述】:我一直在努力与链接表中的附加列建立多对多关系。
这些是我的实体:
@JsonIgnoreProperties( "hibernateLazyInitializer", "handler" )
public class Post
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonIgnore
private List<PostTag> tags = new ArrayList<>();
//getters and setters
public void addTag(Tag tag)
PostTag postTag = new PostTag(this, tag);
tags.add(postTag);
tag.getPosts().add(postTag);
public void removeTag(Tag tag)
for (Iterator<PostTag> iterator = tags.iterator();
iterator.hasNext(); )
PostTag postTag = iterator.next();
if (postTag.getPost().equals(this) &&
postTag.getTag().equals(tag))
iterator.remove();
postTag.getTag().getPosts().remove(postTag);
postTag.setPost(null);
postTag.setTag(null);
@Override
public boolean equals(Object o)
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Post post = (Post) o;
return id == post.id;
@Override
public int hashCode()
return Objects.hash(id);
@JsonIgnoreProperties( "hibernateLazyInitializer", "handler" )
public class Tag
@Id
@GeneratedValue
private Long id;
private String comment;
@OneToMany(mappedBy = "tag", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonIgnore
private List<PostTag> posts = new ArrayList<>();
//getters and setters
@Override
public boolean equals(Object o)
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Tag that = (Tag) o;
return id == that.id;
@Override
public int hashCode()
return Objects.hash(id);
@Entity(name = "PostTag")
@Table(name = "post_tag")
@JsonIgnoreProperties( "hibernateLazyInitializer", "handler" )
public class PostTag
@EmbeddedId
private PostTagId id;
@ManyToOne(fetch = FetchType.LAZY)
@MapsId("postId")
private Post post;
@ManyToOne(fetch = FetchType.LAZY)
@MapsId("tagId")
private Tag tag;
private Integer impact;
public FacilityParticipant(Post post, Tag tag)
this.post = post;
this.tag = tag;
this.id = new PostTagId(post.getId(), tag.getId());
//getters and setters
@Override
public boolean equals(Object o)
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
PostTag that = (PostTag) o;
return Objects.equals(post, that.post) && Objects.equals(tag, that.tag);
@Override
public int hashCode()
return Objects.hash(post, tag);
@Embeddable
public class PostTagId implements Serializable
private Long postId;
private Long tagId;
//getters setters
@Override
public boolean equals(Object o)
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
PostTagId that = (PostTagId) o;
return Objects.equals(postId, that.postId) && Objects.equals(tagId, that.tagId);
@Override
public int hashCode()
return Objects.hash(postId, tagId);
我有一个映射到许多标签的帖子实体和一个映射到许多帖子的标签。链接表是 PostTag,其中包含到双方的映射,以及附加列“影响”。链接表的 PK 映射到一个 Embeddable 表 PostTagId,其中包含来自 Post 和 Tag 的 PK。
当我尝试插入新实体时,我会执行以下操作:
Tag tag1 = new Tag();
Tag tag2 = new Tag();
repository.save(tag1);
repository.save(tag2);
Post post1 = new Post();
Post post2 = new Post();
post1.addTag(tag1);
post1.addTag(tag2);
post2.addTag(tag1);
repository.save(post1);
repository.save(post2);
当尝试插入这些项目时,我收到了错误,我 cannot insert NULL into ("POST_TAG"."ID")
我尝试过的任何东西,要么带有其他错误,要么立即返回。
很可能模型中的某些内容不正确,但我真的无法弄清楚它有什么问题。
整个建模都是基于这篇文章The best way to ...
任何帮助将不胜感激。
谢谢
【问题讨论】:
也许您错过了 PostTag 实体上的 @IdClass(PostTagId.class)? 没有它应该可以工作 这里的最后一个解决方案怎么样:***.com/questions/15130494/… 还是不行:( 【参考方案1】:spring-data-jpa 是 JPA 之上的一层。每个实体都有自己的存储库,您必须处理它。我已经看过上面提到的那个教程,它是针对 JPA 的,它还将 ID 设置为 null,这看起来有点不对劲,可能是你的错误的原因。我没看那么近。为了处理 spring-data-jpa 中的问题,您需要一个单独的链接表存储库。
@Entity
public class Post
@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
private List<PostTag> tags;
@Entity
public class Tag
@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "tag", cascade = CascadeType.ALL, orphanRemoval = true)
private List<PostTag> posts;
@Entity
public class PostTag
@EmbeddedId
private PostTagId id = new PostTagId();
@ManyToOne(fetch = FetchType.LAZY)
@MapsId("postId")
private Post post;
@ManyToOne(fetch = FetchType.LAZY)
@MapsId("tagId")
private Tag tag;
public PostTag()
public PostTag(Post post, Tag tag)
this.post = post;
this.tag = tag;
@SuppressWarnings("serial")
@Embeddable
public class PostTagId implements Serializable
private Long postId;
private Long tagId;
@Override
public boolean equals(Object o)
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
PostTagId that = (PostTagId) o;
return Objects.equals(postId, that.postId) && Objects.equals(tagId, that.tagId);
@Override
public int hashCode()
return Objects.hash(postId, tagId);
并使用它,如上所示:
@Transactional
private void update()
System.out.println("Step 1");
Tag tag1 = new Tag();
Post post1 = new Post();
PostTag p1t1 = new PostTag(post1, tag1);
tagRepo.save(tag1);
postRepo.save(post1);
postTagRepo.save(p1t1);
System.out.println("Step 2");
Tag tag2 = new Tag();
Post post2 = new Post();
PostTag p2t2 = new PostTag(post2, tag2);
postRepo.save(post2);
tagRepo.save(tag2);
postTagRepo.save(p2t2);
System.out.println("Step 3");
tag2 = tagRepo.getOneWithPosts(2L);
tag2.getPosts().add(new PostTag(post1, tag2));
tagRepo.save(tag2);
System.out.println("Step 4 -- better");
PostTag p2t1 = new PostTag(post2, tag1);
postTagRepo.save(p2t1);
请注意,更改很少。我没有明确设置PostTagId
id。这些由持久层处理(在本例中为休眠)。
还请注意,您可以使用自己的 repo 或通过在列表中添加和删除它们来更新 PostTag
条目,因为设置了 CascadeType.ALL
,如图所示。将CascadeType.ALL
用于spring-data-jpa 的问题在于,即使您预取连接表实体,spring-data-jpa 仍然会再次执行此操作。尝试通过 CascadeType.ALL
为新实体更新关系是有问题的。
没有CascadeType
,posts
或tags
列表(应该是集合)都不是关系的所有者,因此添加到它们不会在持久性方面完成任何事情,并且仅用于查询结果.
读取PostTag
关系时,您需要专门获取它们,因为您没有FetchType.EAGER
。 FetchType.EAGER
的问题是如果你不想要连接的开销,如果你把它放在 Tag
和 Post
上,那么你将创建一个递归获取,获取所有 Tags
和 Posts
任何查询。
@Query("select t from Tag t left outer join fetch t.posts tps left outer join fetch tps.post where t.id = :id")
Tag getOneWithPosts(@Param("id") Long id);
最后,始终检查日志。请注意,创建关联需要 spring-data-jpa(我认为是 JPA)来读取现有表以查看关系是新的还是更新的。无论您是自己创建和保存PostTag
,还是即使您预取了列表,都会发生这种情况。 JPA 有一个单独的合并,我认为您可以更有效地使用它。
create table post (id bigint generated by default as identity, primary key (id))
create table post_tag (post_id bigint not null, tag_id bigint not null, primary key (post_id, tag_id))
create table tag (id bigint generated by default as identity, primary key (id))
alter table post_tag add constraint FKc2auetuvsec0k566l0eyvr9cs foreign key (post_id) references post
alter table post_tag add constraint FKac1wdchd2pnur3fl225obmlg0 foreign key (tag_id) references tag
Step 1
insert into tag (id) values (null)
insert into post (id) values (null)
select posttag0_.post_id as post_id1_1_0_, posttag0_.tag_id as tag_id2_1_0_ from post_tag posttag0_ where posttag0_.post_id=? and posttag0_.tag_id=?
insert into post_tag (post_id, tag_id) values (?, ?)
Step 2
insert into post (id) values (null)
insert into tag (id) values (null)
select posttag0_.post_id as post_id1_1_0_, posttag0_.tag_id as tag_id2_1_0_ from post_tag posttag0_ where posttag0_.post_id=? and posttag0_.tag_id=?
insert into post_tag (post_id, tag_id) values (?, ?)
Step 3
select tag0_.id as id1_2_0_, posts1_.post_id as post_id1_1_1_, posts1_.tag_id as tag_id2_1_1_, post2_.id as id1_0_2_, posts1_.tag_id as tag_id2_1_0__, posts1_.post_id as post_id1_1_0__ from tag tag0_ left outer join post_tag posts1_ on tag0_.id=posts1_.tag_id left outer join post post2_ on posts1_.post_id=post2_.id where tag0_.id=?
select tag0_.id as id1_2_1_, posts1_.tag_id as tag_id2_1_3_, posts1_.post_id as post_id1_1_3_, posts1_.post_id as post_id1_1_0_, posts1_.tag_id as tag_id2_1_0_ from tag tag0_ left outer join post_tag posts1_ on tag0_.id=posts1_.tag_id where tag0_.id=?
select posttag0_.post_id as post_id1_1_0_, posttag0_.tag_id as tag_id2_1_0_ from post_tag posttag0_ where posttag0_.post_id=? and posttag0_.tag_id=?
insert into post_tag (post_id, tag_id) values (?, ?)
Step 4 -- better
select posttag0_.post_id as post_id1_1_0_, posttag0_.tag_id as tag_id2_1_0_ from post_tag posttag0_ where posttag0_.post_id=? and posttag0_.tag_id=?
insert into post_tag (post_id, tag_id) values (?, ?)
【讨论】:
以上是关于Spring-Data-JPA ManyToMany 与额外列的关系的主要内容,如果未能解决你的问题,请参考以下文章
spring-data-jpa 1.11.16 带游标的存储过程