JPA создает имя таблицы @ManyToMany не по 'type_type', а по 'type_varname' - PullRequest
0 голосов
/ 20 декабря 2018

Предположим, у нас есть два (или более) класса, один из которых @ ManyToMany ссылается на другие классы: (здесь я упущу множество аннотаций для упрощения)

@Entity
class Newspaper {
    @Id long                    id;
    @ManyToMany Set<Author>     authors     = new HashSet<>();
    @ManyToMany Set<Article>    oldArticles = new HashSet<>();
    @ManyToMany Set<Article>    newArticles = new HashSet<>();
}

@Entity
class Article {
    @Id long id;
}

@Entity
class Author {
    @Id long id;
}

Теперь по умолчанию JPAсоздаст две таблицы:

Newspaper_Author
Newspaper_Article

и даже смешает oldArticles и newArticles в одной таблице, создавая интересные результаты; -)

Теперь этопроблема может быть легко решена путем определения @JoinTable по крайней мере для одной или всех переменных-членов:

    @Entity
class Newspaper {
    @Id long                                                            id;
    @ManyToMany Set<Author>                                             authors     = new HashSet<>();
    @ManyToMany Set<Article>                                            oldArticles = new HashSet<>();
    @ManyToMany @JoinTable(name = "Newspaper_newArticles") Set<Article> newArticles = new HashSet<>();
}

Итак, наконец, перейдем к моему вопросу: Когда только класс определен какэто

@Entity
class Newspaper {
    @Id long                    id;
    @ManyToMany Set<Author>     authors     = new HashSet<>();
    @ManyToMany Set<Article>    oldArticles = new HashSet<>();
    @ManyToMany Set<Article>    newArticles = new HashSet<>();
}

есть ли способ заставить JPA автоматически создавать таблицы

Newspaper_authors
Newspaper_oldArticles
Newspaper_newArticles

ОБНОВЛЕНИЕ: ... и сделатьпроблемы действительно сумасшедшие:

@Entity
class Newspaper {
    @Id long                    id;
    @ManyToMany Set<Author>     authors     = new HashSet<>();
    @ManyToMany Set<OldArticle> oldArticles = new HashSet<>();
    @ManyToMany Set<NewArticle> newArticles = new HashSet<>();
}

@MappedSuperclass
class Article {
    @Id long                id;
    @ManyToMany Set<Author> authors = new HashSet<>();
}

@Entity
class OldArticle extends Article { /* */ }

@Entity
class NewArticle extends Article { /* */ }

@Entity
class Author {
    @Id long id;
}

как я могу определить имена для Article.authors здесь?

1 Ответ

0 голосов
/ 30 декабря 2018

В вашем случае я бы рекомендовал использовать @ Inheritance с одной таблицей для всех типов статей вместо @ MappedSuperclass :

@Data
@NoArgsConstructor
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type")
public abstract class Article {
    @Id
    @GeneratedValue
    private Integer id;

    private String name;

    @ManyToMany
    private Set<Newspaper> newspapers;

    @ManyToMany
    private Set<Author> authors;

    public Article(String name, Set<Newspaper> newspapers, Set<Author> authors) {
        this.name = name;
        this.newspapers = newspapers;
        this.authors = authors;
    }

    @Override
    public String toString() {
        return getClass().getSimpleName() + "{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

Всетипы статей будут храниться в одной таблице, а их типы можно определить с помощью столбца type, который вы задаете в аннотации @DiscriminatorColumn.

Тогда вы сможете использовать один набор статей вNewspaper сущность:

@Data
@NoArgsConstructor
@Entity
public class Newspaper {

    @Id
    @GeneratedValue
    private Integer id;

    private String name;

    @ManyToMany(mappedBy = "newspapers")
    private Set<Author> authors;

    @ManyToMany(mappedBy = "newspapers")
    private Set<Article> articles;

    public Newspaper(String name) {
        this.name = name;
    }
}

Обратите внимание на параметр mappedBy , который необходимо использовать в случае использования двунаправленный ManyToMany.

Конкретные статьи:

@NoArgsConstructor
@Entity
@DiscriminatorValue("FIRST")
public class FirstTypeArticle extends Article {
    public FirstTypeArticle(String name, Set<Newspaper> newspapers, Set<Author> authors) {
        super(name, newspapers, authors);
    }
}

@NoArgsConstructor
@Entity
@DiscriminatorValue("SECOND")
public class SecondTypeArticle extends Article {
    public SecondTypeArticle(String name, Set<Newspaper> newspapers, Set<Author> authors) {
        super(name, newspapers, authors);
    }
}

Примечание к аннотации @DiscriminatorValue, используется для установки значения столбца дискриминатора.

Авторская сущность (также с одним набором статей):

@Data
@NoArgsConstructor
@Entity
public class Author {
    @Id
    @GeneratedValue
    private Integer id;

    private String name;

    @ManyToMany
    private Set<Newspaper> newspapers;

    @ManyToMany(mappedBy = "authors")
    private Set<Article> articles;

    public Author(String name, Set<Newspaper> newspapers) {
        this.name = name;
        this.newspapers = newspapers;
    }
}

Для этого набора объектов в моем демонстрационном проекте Spring Boot 2.1.1 (с базой данных H2) Hibernate создал следующие таблицы без каких-либо дополнительных настроек:

ARTICLE
ARTICLE_AUTHORS
ARTICLE_NEWSPAPERS
AUTHOR
AUTHOR_NEWSPAPERS
NEWSPAPER

Репозитории:

public interface ArticleRepo extends JpaRepository<Article, Integer> {
}

public interface AuthorRepo extends JpaRepository<Author, Integer> {
}

public interface NewspaperRepo extends JpaRepository<Newspaper, Integer> {
}

Пример использования:

@RunWith(SpringRunner.class)
@DataJpaTest
public class ArticleRepoTest {

    @Autowired private ArticleRepo articleRepo;
    @Autowired private AuthorRepo authorRepo;
    @Autowired private NewspaperRepo newspaperRepo;

    private List<Article> articles;

    @Before
    public void setUp() throws Exception {
        List<Newspaper> newspapers = newspaperRepo.saveAll(List.of(
                new Newspaper("newspaper1"),
                new Newspaper("newspaper2")
        ));

        List<Author> authors = authorRepo.saveAll(List.of(
                new Author("author1", new HashSet<>(newspapers)),
                new Author("author2", new HashSet<>(newspapers))
        ));

        articles = articleRepo.saveAll(List.of(
                new FirstTypeArticle("first type article", new HashSet<>(newspapers), new HashSet<>(authors)),
                new SecondTypeArticle("second type article", new HashSet<>(newspapers), new HashSet<>(authors))
        ));
    }

    @Test
    public void findAll() {
        List<Article> result = articleRepo.findAll();
        result.forEach(System.out::println);
        assertThat(result)
                .hasSize(2)
                .containsAll(articles);
    }
}

ОБНОВЛЕНИЕ

Лично мне не нравится использование @Inheritance ...Я также попытался избежать функциональности mappedBy, потому что мне не нужна двунаправленная адресация ...

Конечно, вы можете использовать @MappedSuperclass вместо @Inheritance в Article.И вы можете избежать mappedBy и использовать однонаправленный ManyToMany.

Но в этом случае вам придется сохранять независимые объекты, такие как Author и Article - Newspaper (см. Параметр cascade = CascadeType.MERGE).Что касается меня, то это довольно неудобно (я пытался нейтрализовать его с помощью служебных методов addAuthors и addArticles):

@Data
@NoArgsConstructor
@Entity
public class Newspaper {

    @Id
    @GeneratedValue
    private Integer id;

    private String name;

    @ManyToMany(cascade = CascadeType.MERGE)
    private Set<Author> authors = new HashSet<>();

    @ManyToMany(cascade = CascadeType.MERGE)
    private Set<FirstTypeArticle> firstTypeArticles = new HashSet<>();

    @ManyToMany(cascade = CascadeType.MERGE)
    private Set<SecondTypeArticle> secondTypeArticles = new HashSet<>();

    public Newspaper(String name) {
        this.name = name;
    }

    public Newspaper addAuthors(Author... authors) {
        if (authors != null) {
            this.authors.addAll(Set.of(authors));
        }
        return this;
    }

    public Newspaper addArticles(Article... articles) {
        for (Article article : articles) {
            if (article instanceof FirstTypeArticle) {
                this.firstTypeArticles.add((FirstTypeArticle) article);
            }
            if (article instanceof SecondTypeArticle) {
                this.secondTypeArticles.add((SecondTypeArticle) article);
            }
        }
        return this;
    }
}
@Data
@NoArgsConstructor
@Entity
public class Author {
    @Id
    @GeneratedValue
    private Integer id;

    private String name;

    public Author(String name) {
        this.name = name;
    }
}
@Data
@NoArgsConstructor
@MappedSuperclass
public abstract class Article {
    @Id
    @GeneratedValue
    private Integer id;

    private String name;

    @ManyToMany(cascade = CascadeType.MERGE)
    private Set<Author> authors = new HashSet<>();

    public Article(String name, Author... authors) {
        this.name = name;
        addAuthors(authors);
    }

    public void addAuthors(Author... authors) {
        if (authors != null) {
            this.authors.addAll(Set.of(authors));
        }
    }

    @Override
    public String toString() {
        return getClass().getSimpleName() + "{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

@NoArgsConstructor
@Entity
public class FirstTypeArticle extends Article {
    public FirstTypeArticle(String name, Author... authors) {
        super(name, authors);
    }
}

@NoArgsConstructor
@Entity
public class SecondTypeArticle extends Article {
    public SecondTypeArticle(String name, Author... authors) {
        super(name, authors);
    }
}

Тогда вы получите следующие автоматически сгенерированные таблицы:

AUTHOR
FIRST_TYPE_ARTICLE
FIRST_TYPE_ARTICLE_AUTHORS
SECOND_TYPE_ARTICLE
SECOND_TYPE_ARTICLE_AUTHORS
NEWSPAPER
NEWSPAPER_AUTHORS
NEWSPAPER_FIRST_TYPE_ARTICLES
NEWSPAPER_SECOND_TYPE_ARTICLES

Пример использования

Добавление газет:

newspapers = newspaperRepo.saveAll(List.of(
        new Newspaper("newspaper1"),
        new Newspaper("newspaper2")
));

Добавление авторов:

newspaperRepo.save(newspapers.get(0).addAuthors(
        new Author("author1"),
        new Author("author2")
));

Получение авторов:

authors = authorRepo.findAll();

Добавление статей:

newspaperRepo.save(newspapers.get(0).addArticles(
        new FirstTypeArticle("article1", authors.get(0), authors.get(1)),
        new SecondTypeArticle("article2", authors.get(1))
));
...