【问题标题】:Jpa hibernate problem while querying many to many join table查询多对多连接表时出现Jpa休眠问题
【发布时间】:2024-04-26 15:40:02
【问题描述】:

我在 POST 和 TAG 之间有多对多的关系。每个帖子可以有很多标签,每个标签可以有很多帖子。

我想管理与映射到连接表的实体的关系。所以我有 3 个实体类:PostTagPostTagPostTag@ManyToOne 注释 PostTag 成员。

PostTag 及其嵌入密钥:

@Entity
@Table(name = "post_tag")
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 PostTag() {}
        
  public PostTag(Post post, Tag tag) {
    this.post = post;
    this.tag = tag;
    this.id = new PostTagId(post.getId(), tag.getId());
  }
    
  public PostTagId getId() { return id; }
  public void setId(PostTagId id) { this.id = id; }
  public Post getPost() { return post; }
  public void setPost(Post post) { this.post = post; }
  public Tag getTag() { return tag; }
  public void setTag(Tag tag) { this.tag = tag; }
}

@Embeddable
public class PostTagId implements Serializable {
     
  @Column(name = "post_id")
  private Long postId;
     
  @Column(name = "tag_id")
  private Long tagId;
     
  public PostTagId() {}
     
  public PostTagId(Long postId, Long tagId) {
    this.postId = postId;
    this.tagId = tagId;
  }
     
  public Long getPostId() { return postId; }
  public void setPostId(Long postId) { this.postId = postId; }
  public Long getTagId() { return tagId; }
  public void setTagId(Long tagId) { this.tagId = tagId; }
}

当我需要带有给定标签的帖子时,我想使用这个查询:

@Query(value = "select pt from PostTag pt join fetch pt.post where pt.id.tagId = :tagId")
Set<PostTag> findByTagIdAndFetchPosts(@Param("tagId") Long tagId);

问题在于 Hibernate 创建了这个选择:

    2020-08-25 10:21:57.486 DEBUG 16791 --- [           main] org.hibernate.SQL                        : 
        select
            posttag0_.post_id as post_id1_1_0_,
            posttag0_.tag_tag_id as tag_tag_2_1_0_ 
        from
            post_tag posttag0_ 
        where
            posttag0_.post_id=? 
            and posttag0_.tag_tag_id=?

这会导致以下错误:

    2020-08-25 10:21:57.487  WARN 16791 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 42122, SQLState: 42S22
    2020-08-25 10:21:57.487 ERROR 16791 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : Column "POSTTAG0_.TAG_TAG_ID" not found; SQL statement:
    select posttag0_.post_id as post_id1_1_0_, posttag0_.tag_tag_id as tag_tag_2_1_0_ from post_tag posttag0_ where posttag0_.post_id=? and posttag0_.tag_tag_id=? [42122-200]

这有什么问题?

整件事都在这里:

https://github.com/riskop/jpa_hibernate_spring_boot_many_to_many_managed_on_join_table_problem

【问题讨论】:

    标签: hibernate jpa orm many-to-many


    【解决方案1】:

    当你使用@MapsId 时,hibernate 实际上会忽略@Embeddable 类的相应字段的@Column 注释中提供的列名,并开始使用来自@JoinColumn 的名称。如果@JoinColumn 不存在,则应用默认约定(引用关系属性名称的串联;“_”;被引用主键列的名称)。

    你有:

    @Entity
    public class Tag {
    
        @Id
        @Column(name="TAG_ID")
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
        // ...
    }
    
    @Entity
    @Table(name = "post_tag")
    public class PostTag {
        
        @EmbeddedId
        private PostTagId id;
    
        // There is no @JoinColumn annotation !!!
        // So, the default naming convention is used 
        // "tag" + "_" + "tag_id"
        // "tag_id" is the Tag's entity PK column name
        @ManyToOne(fetch = FetchType.LAZY)
        @MapsId("tagId")
        private Tag tag;
        
        // ...
    }
    

    因此,您可以通过以下方式修复映射:

    @Entity
    @Table(name = "post_tag")
    public class PostTag {
        
        @EmbeddedId
        private PostTagId id;
    
        // by chance the default naming convention 
        // lead to the same column name for this case
        @MapsId("postId")
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "post_id") 
        private Post post;
        
        @MapsId("tagId")
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "tag_id")
        private Tag tag;
    
        // ...
    }
    

    您可以从PostTagId 字段中删除@Column 注释:

    @Embeddable
    public class PostTagId implements Serializable {
     
        private Long postId;
     
        private Long tagId;
        // ...
    }
    

    因为它们被忽略了。

    【讨论】: