i have many to many relationship between book and author, i have 3 tables: author, book and author_book.
@Entity() @Table(name = "author") public class Author implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) @JoinTable( name = "author_book", joinColumns = @JoinColumn(name = "author_id"), inverseJoinColumns = @JoinColumn(name = "book_id") ) private List<Book> authorBooks = new ArrayList<Book>(); public Author() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Book> getAuthorBooks() { return authorBooks; } public void setAuthorBooks(List<Book> authorBooks) { this.authorBooks = authorBooks; } @Override public String toString() { return "Author{" + "name=" + name + ", authorBooks=" + authorBooks + '}'; } } @Entity() @Table(name = "book") public class Book implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; @ManyToMany(mappedBy = "authorBooks", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List<Author> bookAuthors = new ArrayList<Author>(); public Book() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Author> getBookAuthors() { return bookAuthors; } public void setBookAuthors(List<Author> bookAuthors) { this.bookAuthors = bookAuthors; } @Override public String toString() { return "Book{" + "name=" + name + ", bookAuthors=" + bookAuthors + '}'; } }
i can add data to db without a problem, but when i want to get an author or a book by its id
Optional<Author> optionalAuthor = authorReposi.findById(1L); System.out.println("Author: " + optionalAuthor.get().toString());
i get an error: LazyInitialization failed to lazily …
I want to use FetchType.LAZY and get the instance of author or book.
Thank you for your time.
Advertisement
Answer
So, read the fine manual: 6.3.10. Configuring Fetch- and LoadGraphs.
Your issue is simply that your toString()
methods are recursive. Authors
says to print Books
, and Books
says to print Authors
. Pro tip: success is in the details.
For a load
or fetch
you need to use an EntityGraph
from JPA
to specify the joined attributes. So:
@Entity() @Table(name = "author") @NamedEntityGraph(name = "Book.detail", attributeNodes = @NamedAttributeNode("books")) @Getter @Setter @Builder @AllArgsConstructor @NoArgsConstructor public class Author { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; @ManyToMany private List<Book> books; }
and
@Entity() @Table(name = "book") @Getter @Setter @Builder @AllArgsConstructor @NoArgsConstructor public class Book { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; @ManyToMany(mappedBy = "books") private List<Author> authors; }
With repositories:
public interface AuthorRepository extends JpaRepository<Author, Long>{ @EntityGraph(value = "Book.detail", type = EntityGraphType.LOAD) Author getById(Long id); }
and
public interface BookRepository extends JpaRepository<Book, Long>{ }
Then you must print what you want yourself. Basically, putting toStrings
in Entities
generally causes problems, but you should also RTFM.
private void read(Long id) { Author a = authorRepository.getById(id); System.out.println("Author: " + a.getName()); for( Book b: a.getBooks()) { System.out.println("tBook: " + b.getName()); } }
Finally, I avoid using Cascade
annotations like the plague. They are complicated annotations. Also, ManyToMany
is FetchType.LAZY
by default. The important annotation is the mappedBy
annotation. This tells you which entity owns the relationship. The owning entity is the one responsible for persisting relations. The other side of bidirectional annotations are really only for queries. There is no need to make new ArrayList
in the entities since they will be thrown away anyway during queries. Just create a list when you need to persist a new Author
entity with relations, otherwise use the lists returned by the queries.
private Author save() { Book b = bookRepository.save(Book.builder().name("b1").build()); return authorRepository.save(Author.builder().name("a1").books(Collections.singletonList(b)).build()); }