Связи Spring-Data-JPA ManyToMany с дополнительным столбцом - PullRequest
0 голосов
/ 04 октября 2018

Я изо всех сил пытался установить связь «многие ко многим» с дополнительным столбцом в таблице ссылок.

Это мои сущности:

    @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 таблицы ссылок отображается на встраиваемую таблицу PostTagId, которая содержит PK из Post и Tag.

Когда я пытаюсь вставить новые объекты, я делаю следующее:

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);

При попытке вставить эти элементы я получаю сообщение об ошибке, что я не могу вставить NULL в ("POST_TAG "." ID ")

Все, что я пробовал, либо идет с другими ошибками, либо возвращается обратно.

Скорее всего, что-то из моделине правильно, но я действительно не могу понять, что с ним не так.

Все моделирование основано на этой статье Лучший способ ...

Любая помощь будет принята с благодарностью.

Спасибо

1 Ответ

0 голосов
/ 04 октября 2018

Spring-data-jpa - это слой поверх JPA.У каждой сущности есть свой собственный репозиторий, с которым вам приходится иметь дело.Я видел этот учебник, упомянутый выше, и он для JPA, и он также устанавливает идентификаторы в нуль, что кажется немного неправильным и, вероятно, причиной вашей ошибки.Я не смотрел так близко.Для решения проблемы в 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.Они обрабатываются постоянным слоем (в данном случае спящим).

Также обратите внимание, что вы можете обновлять записи PostTag либо explicity с его собственным репо, либо добавляя и удаляя их из списка, так как 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 (?, ?)
...