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

时间:2017-07-08 16:47:12

标签: java spring hibernate spring-mvc spring-data-jpa

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

The code is on github

Book.class

@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);
}

}

Author.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中的测试代码snapper

    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);

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

非常感谢。

3 个答案:

答案 0 :(得分:1)

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

您正在使用双向 ManyToMany关系。因此,当您将图书添加到图书时,您必须使用图书手动“链接”作者

@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,以避免发生Stackoverflow异常(您必须在作者类中执行此操作):< / p>

@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}

答案 1 :(得分:0)

您可以将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();

答案 2 :(得分:0)

当我使用此代码时,迭代Set,在控制台上崩溃:

org.hibernate.LazyInitializationException:无法初始化代理 - 没有会话,加载不受欢迎

所以,我在Stackoverflow.com上搜索这些技巧。

This answer help me

添加

FISRT - 初级书籍

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

使用@JB Nizet建议,谢谢!

SECOND - 在结束链接中添加FetchType.EAGER

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

谢谢大家。