Spring Data JPA:查询ManyToMany,如何从映射类中获取数据?

Posted

技术标签:

【中文标题】Spring Data JPA:查询ManyToMany,如何从映射类中获取数据?【英文标题】:Spring Data JPA: query ManyToMany, How Can I get the data from mapped Class? 【发布时间】:2017-12-12 19:11:46 【问题描述】:

我正在使用 Spring Data JPA 和 H2 数据库开发 Spring Boot 应用程序。我正在使用 spring-data-jpa。 当我使用 ManyToMany 映射器类来获取另一个类的数据时。但我发现它是NULL。

The code is on github

书籍类

@Entity
public class Book implements Serializable 

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue
private Integer id;

private String name;

@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "BOOK_AUTHOR", joinColumns = 
        @JoinColumn(name = "BOOK_ID", referencedColumnName = "ID"), inverseJoinColumns = 
        @JoinColumn(name = "AUTHOR_ID", referencedColumnName = "ID"))
private Set<Author> authors;

public Book() 
    super();


public Book(String name) 
    super();
    this.name = name;
    this.authors = new HashSet<>();


public Book(String name, Set<Author> authors) 
    super();
    this.name = name;
    this.authors = authors;


public Integer getId() 
    return id;


public void setId(Integer id) 
    this.id = id;


public String getName() 
    return name;


public void setName(String name) 
    this.name = name;


public Set<Author> getAuthors() 
    return authors;


public void setAuthors(Set<Author> authors) 
    this.authors = authors;


@Override
public String toString() 
    return String.format("Book [id=%s, name=%s, authors=%s]", id, name, authors);



作者.class

@Entity
public class Author implements Serializable 

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue
private Integer id;

private String name;

@ManyToMany(mappedBy = "authors")
private Set<Book> books;


//why CAN NOT GET the data when using these code else ?
//    @ManyToMany(cascade = CascadeType.ALL)
//    @JoinTable(name = "BOOK_AUTHOR", joinColumns = 
//            @JoinColumn(name = "AUTHOR_ID", referencedColumnName = "ID"), 
//inverseJoinColumns = 
//            @JoinColumn(name = "BOOK_ID", referencedColumnName = "ID"))
//    private Set<Book> books;

public Author() 
    super();


public Author(String name) 
    super();
    this.name = name;


public Integer getId() 
    return id;


public void setId(Integer id) 
    this.id = id;


public String getName() 
    return name;


public void setName(String name) 
    this.name = name;


public Set<Book> getBooks() 
    return books;


public void setBooks(Set<Book> books) 
    this.books = books;


@Override
public String toString() 
    return String.format("Author [id=%s, name=%s, books=%s]", id, name, books);



在 test.class 中测试代码快照器

    List<Book> books = bookRepository.findAll();
    for (Book it : books) 
        Set<Author> authors = it.getAuthors();
        //CAN get authors data.
        System.out.println(authors.size());
    

    assertThat(bookRepository.findAll()).hasSize(2);

    List<Author> authors = authorRepository.findAll();
    for (Author it : authors) 
        //CAN NOT get books data ? Why and HOW ?
        //HOW can I get the books data ? Or other ways ? 
        // thanks 
        Set<Book> books1 = it.getBooks();
        assertThat(books1 == null);
    

    assertThat(authorRepository.findAll()).hasSize(3);

我的代码有错误吗? 还是其他方式?

感谢有很大不同。

【问题讨论】:

Author 对象在访问其getBooks 方法时处于什么生命周期状态?在此之前是否加载了数据? JPA 提供者有一个日志告诉你这些事情。数据库中的数据是否正确? 你的代码应该可以工作,除非你刚刚在同一个事务中创建了书籍和作者,而忘记在作者中设置书籍。 JPA 从不将集合设置为 null,您也不应该这样做。将您的集合初始化为空集合。 【参考方案1】:

将 FetchType 设置为 EAGER 不好,因为它效率不高。 当然,您必须先初始化authorsbooks


您正在使用 双向 多对多关系。因此,当您将 Authors 添加到此 Book 时,您必须手动将其“链接”到 Book

@ManyToMany(cascade = CascadeType.ALL)
private final Set<Author> authors = new HashSet<>();
// ...
public void setAuthors(Set<Author> authors) 
    for (Author author : authors) 
        author.getBooks().add(this);
        this.authors.add(author);
    

(关注author.getBooks().add(this);

你需要分别改变你的构造函数:

public Book(String name, Set<Author> authors) 
    this.name = name;
    setAuthors(authors);

另外我建议更正 toString 方法 - 从中​​删除 authors,以避免发生 *** 异常(您也必须在 Author 类中执行此操作):

@Override
public String toString() 
    return String.format("Book [id=%s, name=%s]", id, name);

你的测试可以正常工作:

@Before
public void init() 
    Author lewis = new Author("Lewis");
    Author mark = new Author("Mark");
    Author peter = new Author("Peter");

    Book spring = new Book("Spring in Action", new HashSet<>(asList(lewis, mark)));
    Book springboot = new Book("Spring Boot in Action", new HashSet<>(asList(lewis, peter)));

    bookRepository.save(Arrays.asList(spring, springboot));


@Test
public void findAll() 
    List<Book> books = bookRepository.findAll();
    assertThat(books).hasSize(2);
    for (Book book : books) 
        Set<Author> bookAuthors = book.getAuthors();
        assertThat(bookAuthors).isNotNull();
        assertThat(bookAuthors.size()).isGreaterThan(0);

        System.out.println(book);
        bookAuthors.forEach(System.out::println);
    

    List<Author> authors = authorRepository.findAll();
    assertThat(authors).hasSize(3);
    for (Author author : authors) 
        Set<Book> authorBooks = author.getBooks();
        assertThat(authorBooks).isNotNull();
        assertThat(authorBooks.size()).isGreaterThan(0);

        System.out.println(author);
        authorBooks.forEach(System.out::println);
        

请参阅Hibernate User Guide 了解更多信息。


一般不要将cascade = CascadeType.ALL 用于ManyToMany,因为在这种情况下实体是独立的。请改用cascade = CascadeType.PERSIST, CascadeType.MERGE

【讨论】:

我将它提交到我的 github 和 commet。 project code【参考方案2】:

您可以在实体中使用mappedBy = "", cascade = CascadeType.ALL, fetch = FetchType.LAZY。在方法之前使用@Transactional(propagation = Propagation.REQUIRED) 注释并在类上使用@Transactional。当您获取数据时,在多对多关系实体 lilnk 上使用 .size() 方法:当您获取 Author 时使用 Author.getBooks().size();,您可以获得作者的启动详细信息。

例子

FOrd fOrd = orderRepo.findOne(id);
    fOrd.getFOrdItems().size();
    fOrd.getFOrdDetails().size();

【讨论】:

【参考方案3】:

当我使用这个代码迭代 Set 时,控制台崩溃:

org.hibernate.LazyInitializationException: 无法初始化代理 - 没有 Session,encore un fois

所以,我在 ***.com 上搜索了这个提示。

This answer help me

只需添加

FISRT - 初始书籍

@ManyToMany(mappedBy = "authors")
private Set<Book> books = new HashSet<Book>();

使用@JB Nizet 建议,谢谢!

第二个 - 在结束链接中添加 FetchType.EAGER

@ManyToMany(mappedBy = "posts", fetch = FetchType.EAGER)
@JsonBackReference

谢谢大家。

【讨论】:

以上是关于Spring Data JPA:查询ManyToMany,如何从映射类中获取数据?的主要内容,如果未能解决你的问题,请参考以下文章

spring Data jpa 一对多关联 动态查询怎么写

Spring Data 系列学习Spring Data JPA 基础查询

Spring Data 系列学习Spring Data JPA 自定义查询,分页,排序,条件查询

spring-data-jpa 存储库模式与 Querydsl 查询模式有啥区别?

Spring Data 系列学习Spring Data JPA 自定义查询,分页,排序,条件查询

spring data jpa 动态查询(mysql)