Проблема гибернации Jpa при запросе таблицы объединения «многие ко многим»

#hibernate #jpa #orm #many-to-many

#переход в спящий режим #jpa #orm #многие ко многим

Вопрос:

У меня есть связь «многие ко многим» между записями и тегами. У каждого сообщения может быть много тегов, у каждого тега может быть много записей.

Я хотел бы управлять связью с объектом, сопоставленным с таблицей объединения. Итак, у меня есть 3 класса сущностей: Post , Tag , PostTag . PostTag имеет @ManyToOne аннотированные Post и Tag члены.

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 создает этот select:

     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

Ответ №1:

При использовании @MapsId hibernate фактически игнорирует имя столбца, указанное в @Column аннотации соответствующего поля @Embeddable класса, и начинает использовать имя из @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;

    // ...
}
  

и вы можете удалить @Column аннотации из PostTagId полей:

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

поскольку они игнорируются.