In your case, I would recommend to use @Inheritance with a single table for all type of articles instead of @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 + '\'' +
'}';
}
}
All types of articles will be stored in the single table and their types you can determine with column type which you set in @DiscriminatorColumn annotation.
Then you will be able to use a single set of articles in the Newspaper entity:
@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;
}
}
Pay attention to parameter mappedBy which you have to use in case of using bi-directional ManyToMany.
Concrete articles:
@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);
}
}
Note to the annotation @DiscriminatorValue, it used to set a value of discriminator column.
The author entity (also with a single set of articles):
@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;
}
}
For these set of entities in my Spring Boot 2.1.1 demo project (with H2 database) Hibernate has created the following tables without any additional settings:
ARTICLE
ARTICLE_AUTHORS
ARTICLE_NEWSPAPERS
AUTHOR
AUTHOR_NEWSPAPERS
NEWSPAPER
Repositories:
public interface ArticleRepo extends JpaRepository<Article, Integer> {
}
public interface AuthorRepo extends JpaRepository<Author, Integer> {
}
public interface NewspaperRepo extends JpaRepository<Newspaper, Integer> {
}
Usage example:
@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);
}
}
UPDATE
I personally do not like using @Inheritance...
I also tried to avoid the mappedBy functionality, because I neither need bidirectional addressing...
Of cause, you can use @MappedSuperclass instead of @Inheritance in Article. And you can avoid mappedBy and use unidirectional ManyToMany.
But in this case, you will have to save independent entities such Author and Article through Newspaper only (see cascade = CascadeType.MERGE parameter). As for me it's rather inconvenient (I tried to neutralize it with utility methods addAuthors and 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);
}
}
Then you will get the following autogenerated tables:
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
Usage example
Adding newspapers:
newspapers = newspaperRepo.saveAll(List.of(
new Newspaper("newspaper1"),
new Newspaper("newspaper2")
));
Adding authors:
newspaperRepo.save(newspapers.get(0).addAuthors(
new Author("author1"),
new Author("author2")
));
Getting authors:
authors = authorRepo.findAll();
Adding articles:
newspaperRepo.save(newspapers.get(0).addArticles(
new FirstTypeArticle("article1", authors.get(0), authors.get(1)),
new SecondTypeArticle("article2", authors.get(1))
));