hibernate - JPA 不是通过“type_type”而是通过“type_varname”创建 @ManyToMany 表名
问题描述
假设我们有两个(或更多)类,其中一个是@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
这里定义名称?
解决方案
在您的情况下,我建议将@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
in Article
。您可以避免mappedBy
和使用单向多对多。
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))
));
推荐阅读
- amazon-web-services - 如何在 CDK (AWS) 中将安全组添加到 VPC 端点
- testing - 如何使用 testRigor 测试短信 (SMS)?
- powerbi - Power BI - 如何在矩阵表中查看零
- azure - Azure KQL 正则表达式使用 extract_all() 为命名捕获组捕获句子
- c - 闰年问题中逻辑运算符的使用
- ios - 在一个客观的 c ios 项目中,在哪里可以找到数据库模式?
- javascript - 使用 Node.js 将 SQL 对象保存到文件
- r - 如何在列名中间按字符折叠数据框列?
- python - 如何仅根据在单独数据框中找到的列值将行保留在数据框中
- string - 具有自定义连接表名称的 EFCore 多对多配置