0

I want to create a simple many-to-many relation by using spring jpa and hibernate, here's the code:

@Entity
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    @Column(name = "name")
    private String name;

    @ManyToMany(cascade = CascadeType.ALL,
    fetch = FetchType.LAZY)
    @JoinTable(name = "book_publisher",
        joinColumns = @JoinColumn(name = "book_id", referencedColumnName = "id"),
        inverseJoinColumns = @JoinColumn(name = "publisher_id", referencedColumnName = "id"))
    private Set<Publisher> publishers;

and

@Entity
public class Publisher {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    private String name;

    @ManyToMany(mappedBy = "publishers"
    , fetch = FetchType.LAZY)
    @Nullable
    private Set<Book> books = new HashSet<>();

    public Publisher(String name) {
        this.name = name;
    }

The collections are all lazily fetched as the code shown above.

And I create two repositories of there two entities:

public interface PublisherRepository extends JpaRepository<Publisher, Integer>{
}
public interface BookRepository extends JpaRepository<Book, Integer>{

}

And I create a simple controller:

    @GetMapping("/getPublisher/{id}")
    public Publisher getPublisher(@PathVariable Integer id) {
        return publisherRepository.findById(id).get();
    }

something strange happens:

when I make a http call via curl, I received such response:

{"id":1,"name":"YanYan","books":[{"id":1,"name":"Avengers","publishers":[{"id":1,"name":"YanYan","books":[{"id":1,"name":"Avengers","publishers":[{"id":1,"name":"YanYan","books":[{"id":1,"name":"Avengers","publishers":[{"id":1,"name":"YanYan","books":[{"id":1,"name":"Avengers","publishers":[{"id":1,"name":"YanYan","books":[{"id":1,"name":"Avengers","publishers":[{"id" .....

which indicates none of them are lazily fetched, this causes a infinite loop.

Can anyone tell me why?

Dai Niu
  • 479
  • 7
  • 9

1 Answers1

2

I can see 2 problems:

  1. Lazy loading:

Check if spring.jpa.open-in-view is explicitely configured. Unfortunately, the default is true

You can get more info about this setting at: What is this spring.jpa.open-in-view=true property in Spring Boot?

If you dont have it configured, you may receive a warning on startup:

WebConfiguration$JpaWebMvcConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
  1. JSON serialisation

You have 2 entities that cross reference each other, when you serialise a publisher you also serialiseits books, but the book has publisher you already visited. You enter an endless loop in the serialisation phase.

Either use JSON annotations to exclude one side of the relation from serialisation, or use custom DTOs to transfer data.

Update

I expect when you set spring.jpa.open-in-view = false and you don't specify what to fetch, you will start to have LazyInitializationException. This is because you still try to access the fields while serialising them to JSON (but now the objects are not attached to a session). Contrary to your comment, this is a proof that collection is loaded lazily (that means, you have a proxy instead of a collection. You can access this proxy, which forces loading, but only if the session is still open - same transaction or open-in-view setting).

My suggestion: attack JSON serialisation first, this is the real bug. Worry about fetching strategy after that is fixed.

Lesiak
  • 22,088
  • 2
  • 41
  • 65
  • Thank you for give me a great answer! I tried to set spring.jpa.open-in-view = false, but I get an exception: `failed to lazily initialize a collection of role` Something seems like I try to access the collection but the collection has not been loaded because is hasn't been loaded ( loaded lazily). That's pretty weird. I guess that's why even if I set fetch type to lazy I still get all collection loaded eagerly. Could you tell me why? thank you pretty much, sir. – Dai Niu May 17 '19 at 14:43
  • Updated the answer. – Lesiak May 17 '19 at 15:02
  • Thank u very much, sir. I added `@JsonIgnore` to both side (owning side and reverse side), and I try to fetch collections in a `@transactional` method. They works just as expected! thank u! – Dai Niu May 18 '19 at 11:17