I am trying to create an ul
which has a li
for each review
in the Set<Review> reviews
from the book
object that I send back from the server. The result is seemingly a massive internal server error, I get a very long stack-trace printed out to the terminal, I have no idea what might be the problem. If I comment out the ul
block, everything works fine.
The error (opens new link, pastebin) (not the full error, it did not fit in VSCODE terminal.
book.html
<div style="width: 100%; padding-top: 25px; display: flex; justify-content: center;">
<div style="width: 18rem;">
<img src="..." alt="Card image cap">
<div >
<h5 th:text="${book.name}" >Book name here.</h5>
<p th:text="${book.author}" >Author name here.</p>
<p th:text="${book.isbn}" >ISBN number here.</p>
</div>
<ul >
<li th:each="review : ${book.reviews}" th:text="${review.review}" >Review goes here.</li>
</ul>
</div>
</div>
BookController.java
package com.domain.congregentur.book;
import ...
@Controller
@RequestMapping("api/books")
public class BookController {
public BookService bookService;
@Autowired
public BookController(BookService bookService) {
this.bookService = bookService;
}
...
@GetMapping("/{id}")
public String find(@PathVariable("id") Long id, Model model) {
Optional<Book> book = bookService.findById(id);
if (book.isPresent()) {
model.addAttribute("book", book.get());
return "book";
}
return "books";
}
...
}
Book.java
package com.domain.congregentur.book;
import ...
@Entity
@Table(name = "Books")
@EqualsAndHashCode
@ToString
@Getter
@Setter
public class Book implements Serializable {
@Id
@SequenceGenerator(name = "book_sequence", sequenceName = "book_sequence", allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "book_sequence")
private Long id;
@NotNull
private String isbn;
@NotNull
private String name;
@NotNull
private String author;
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "book")
private Set<Review> reviews;
Book() {
}
public Book(Long id, String isbn, String name, String author) {
this.id = id;
this.isbn = isbn;
this.name = name;
this.author = author;
}
public Book(String isbn, String name, String author) {
this.isbn = isbn;
this.name = name;
this.author = author;
}
public Book updateWith(Book book) {
return new Book(
this.id,
book.isbn,
book.name,
book.author);
}
}
Review.java
package com.domain.congregentur.review;
import ...
@Entity
@Table(name = "Reviews")
@EqualsAndHashCode
@ToString
@Setter
@Getter
public class Review {
@Id
@SequenceGenerator(name = "review_sequence", sequenceName = "review_sequence", allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "review_sequence")
private Long id;
@NotNull
private String review;
@ManyToOne
@JoinColumn(name = "isbn", referencedColumnName = "isbn")
private Book book;
Review() {
}
public Review(String review, Book book) {
this.review = review;
this.book = book;
}
}
Might be worth mentioning that this is how I instantiate some random test-data to my db to test with, I don't know if this is an appropriate way of doing this and it may or may not be relevant to the problem. Feel free to educate me on this.
DataLoader.java
package com.domain.congregentur.dataloader;
import ...
@Component
public class DataLoader implements ApplicationRunner {
private BookRepository bookRepository;
private ReviewRepository reviewRepository;
@Autowired
public DataLoader(BookRepository bookRepository, ReviewRepository reviewRepository) {
this.bookRepository = bookRepository;
this.reviewRepository = reviewRepository;
}
public void run(ApplicationArguments args) {
Book b1 = new Book("9780812969641", "In Search of Lost Time", "Marcel Proust");
Book b2 = new Book("9781772267143", "Swann's Way", "Marcel Proust");
Book b3 = new Book("9780099469698", "Don Quixote", "Miguel de Cervantes");
bookRepository.saveAll(List.of(b1, b2, b3));
List<Review> reviews = List.of(
new Review("Book1 -> Review1", b1),
new Review("Book1 -> Review2", b1),
new Review("Book2 -> Review1", b2),
new Review("Book2 -> Review2", b2),
new Review("Book3 -> Review1", b3),
new Review("Book3 -> Review2", b3));
reviewRepository.saveAll(reviews);
}
}
EDIT: After having looked at the stack trace for a bit, it is seemingly repeating the following part but with slightly different numbers at some specific places.
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:591) ~[hibernate-core-5.6.3.Final.jar:5.6.3.Final]
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:149) ~[hibernate-core-5.6.3.Final.jar:5.6.3.Final]
at org.hibernate.collection.internal.PersistentSet.hashCode(PersistentSet.java:458) ~[hibernate-core-5.6.3.Final.jar:5.6.3.Final]
at com.domain.congregentur.book.Book.hashCode(Book.java:26) ~[classes/:na]
at com.domain.congregentur.review.Review.hashCode(Review.java:22) ~[classes/:na]
at java.base/java.util.HashMap.hash(HashMap.java:340) ~[na:na]
at java.base/java.util.HashMap.put(HashMap.java:608) ~[na:na]
at java.base/java.util.HashSet.add(HashSet.java:220) ~[na:na]
at java.base/java.util.AbstractCollection.addAll(AbstractCollection.java:352) ~[na:na]
at org.hibernate.collection.internal.PersistentSet.endRead(PersistentSet.java:355) ~[hibernate-core-5.6.3.Final.jar:5.6.3.Final]
at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollection(CollectionLoadContext.java:239) ~[hibernate-core-5.6.3.Final.jar:5.6.3.Final]
at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:224) ~[hibernate-core-5.6.3.Final.jar:5.6.3.Final]
at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:198) ~[hibernate-core-5.6.3.Final.jar:5.6.3.Final]
at org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl.endLoading(CollectionReferenceInitializerImpl.java:154) ~[hibernate-core-5.6.3.Final.jar:5.6.3.Final]
at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.finishLoadingCollections(AbstractRowReader.java:232) ~[hibernate-core-5.6.3.Final.jar:5.6.3.Final]
at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.finishUp(AbstractRowReader.java:190) ~[hibernate-core-5.6.3.Final.jar:5.6.3.Final]
at org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl.extractResults(ResultSetProcessorImpl.java:96) ~[hibernate-core-5.6.3.Final.jar:5.6.3.Final]
at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:105) ~[hibernate-core-5.6.3.Final.jar:5.6.3.Final]
at org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer.initialize(AbstractLoadPlanBasedCollectionInitializer.java:87) ~[hibernate-core-5.6.3.Final.jar:5.6.3.Final]
at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:705) ~[hibernate-core-5.6.3.Final.jar:5.6.3.Final]
at org.hibernate.event.internal.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:76) ~[hibernate-core-5.6.3.Final.jar:5.6.3.Final]
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:107) ~[hibernate-core-5.6.3.Final.jar:5.6.3.Final]
at org.hibernate.internal.SessionImpl.initializeCollection(SessionImpl.java:2203) ~[hibernate-core-5.6.3.Final.jar:5.6.3.Final]
at org.hibernate.collection.internal.AbstractPersistentCollection$4.doWork(AbstractPersistentCollection.java:595) ~[hibernate-core-5.6.3.Final.jar:5.6.3.Final]
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:264) ~[hibernate-core-5.6.3.Final.jar:5.6.3.Final]
CodePudding user response:
This is because you are using the @EqualsAndHashCode
Lombok annotation. There is an error (possibly recursive, since your stack trace is large, I am not sure) when getting the hashcode of the Review JPA entity.
The Lombok auto-generated hashcode method in Review entity will call the Book entity, which tries to get the hashcode of the Set of Reviews. This Set needs to be initialized first before it can be read.
at com.domain.congregentur.book.Book.hashCode(Book.java:26) ~[classes/:na]
at com.domain.congregentur.review.Review.hashCode(Review.java:22) ~[classes/:na]
You can fix this with 2 options.
- You can implement the hashcode and equals method on your own, using only the primary key and the object's attributes.
Here is a good article explaining in great detail.
- You can use the Lombok annotations, but ensure that equals, hashcode and Getter/Setter methods are used in the right way. Here are 2 answers that cover this topic in detail.