为什么以下 Thymeleaf 模板处理不起作用?

Why is the following Thymeleaf template processing not working?

我正在尝试创建一个 ul,它在我发回的 book 对象的 Set<Review> reviews 中的每个 review 都有一个 li从服务器。结果似乎是一个巨大的内部服务器错误,我在终端上打印了一个很长的堆栈跟踪,我不知道可能是什么问题。如果我注释掉 ul 块,一切正常。

The error (opens new link, pastebin)(不是完整的错误,它不适合 VSCODE 终端。

book.html

<div class="container" style="width: 100%; padding-top: 25px; display: flex; justify-content: center;">
        <div class="card" style="width: 18rem;">
            <img class="card-img-top" src="..." alt="Card image cap">
            <div class="card-body">
              <h5 th:text="${book.name}" class="card-title">Book name here.</h5>
              <p th:text="${book.author}" class="card-text">Author name here.</p>
              <p th:text="${book.isbn}" class="card-text">ISBN number here.</p>
            </div>
            <ul class="list-group list-group-flush">
              <li th:each="review : ${book.reviews}" th:text="${review.review}" class="list-group-item">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;
    }

}

可能值得一提的是,这就是我将一些随机测试数据实例化到我的数据库中进行测试的方式,我不知道这是否是一种合适的方式,它可能与以下内容相关,也可能不相关问题。请随时就此对我进行教育。

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

}

稍微查看堆栈跟踪后,它似乎在重复以下部分,但在某些特定位置的数字略有不同。

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

这是因为您正在使用 @EqualsAndHashCode Lombok 注释。获取 Review JPA 实体的哈希码时出现错误(可能是递归的,因为你的堆栈跟踪很大,我不确定)。

评论实体中的 Lombok 自动生成哈希码方法将调用 Book 实体,该实体尝试获取评论集的哈希码。这个Set需要先初始化才能读取。

at com.domain.congregentur.book.Book.hashCode(Book.java:26) ~[classes/:na]
at com.domain.congregentur.review.Review.hashCode(Review.java:22) ~[classes/:na]

您可以使用 2 个选项解决此问题。

  1. 您可以自己实现 hashcode 和 equals 方法,仅使用主键和对象的属性。

这里有一篇很好的文章,解释的很详细。

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

  1. 您可以使用 Lombok 注释,但要确保以正确的方式使用 equals、hashcode 和 Getter/Setter 方法。这里有 2 个答案详细介绍了这个主题。

Lombok with hibernate

How do I create a safe Lombok JPA entity?