В вашем случае я бы рекомендовал использовать @ 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))
));