Home > Net >  Why is the following Thymeleaf template processing not working?
Why is the following Thymeleaf template processing not working?

Time:12-26

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

}

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.

  1. 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.

https://thorben-janssen.com/lombok-hibernate-how-to-avoid-common-pitfalls/#Don8217t_Use_EqualsAndHashCode

  1. 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.

Lombok with hibernate

How do I create a safe Lombok JPA entity?

  • Related