0

I started working on my project and wanted to use Hibernate along with JpaRepositories. Since I'm not familiar with these that might be a dumb question, however, I can not find out why are my associated collections initialized eagerly.

My classes (simplified):

@Entity(name = "board")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "Board_Type")
public abstract class Board {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "board_id", updatable = false)
    private Long boardId;

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

    @ManyToMany
    @JoinTable(name = "board_status",
                joinColumns = {@JoinColumn(name = "fk_board")},
                inverseJoinColumns = {@JoinColumn(name = "fk_status")})
    private Set<Status> availableStatuses = new HashSet<>();

    @OneToMany(mappedBy = "board", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
    @JsonManagedReference
    private List<Task> taskList = new ArrayList<>();

Sub-class:

@Entity
@DiscriminatorValue(value = "Simple_Board")
public class SimpleBoard extends Board{
    @ManyToOne(fetch = FetchType.LAZY)
    @JsonBackReference
    private User owner;

Task class:

@Entity(name = "task")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Task {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "task_id)
    private Long taskId;

    private String taskName;

    @OneToMany(mappedBy = "task", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
    @JsonManagedReference
    private List<Comment> commentList = new ArrayList<>();;

    @ManyToOne(fetch = FetchType.LAZY)
    @JsonBackReference
    private User creator;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "board_id")
    @JsonBackReference
    private Board board;

Comment class:

@Entity(name = "comment")
public class Comment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "comment_id", updatable = false)
    private Long commentId;

    @ManyToOne(fetch = FetchType.LAZY)
    @JsonBackReference
    private User creator;

    private String content;

    private LocalDateTime creationDate;

    @ManyToOne(fetch = FetchType.LAZY)
    @JsonBackReference
    private Task task;

User class:

@Entity(name = "user")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "User_Type")
public abstract class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id", updatable = false)
    private Long userId;

    private String login;

    private String password;

    private String email;

    private LocalDateTime registerDate;

User sub-class:

@Entity
@DiscriminatorValue(value = "App_User")
public class ApplicationUser extends User{

    private String firstName;

    private String lastName;

    @OneToMany(mappedBy = "owner", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
    @JsonManagedReference
    private List<SimpleBoard> personalBoards = new ArrayList<>();

    @OneToMany(mappedBy="creator", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
    @JsonManagedReference
    private List<Task> userTasks = new ArrayList<>();

Endpoint called to get Board with given Id:

@RestController
@RequestMapping("/board")
public class SimpleBoardController {
    private ApplicationUserService applicationUserService;
    private SimpleBoardService simpleBoardService;

    @Autowired
    public SimpleBoardController(SimpleBoardService simpleBoardService, ApplicationUserService applicationUserService) {
        this.simpleBoardService = simpleBoardService;
        this.applicationUserService = applicationUserService;
    }

    @GetMapping(value = "/{boardId}")
    public SimpleBoard getBoard(@PathVariable Long id){
        return simpleBoardService.getBoardById(id);
    }

Service method:

    @Override
    public SimpleBoard getBoardById(Long id) {
        return simpleBoardDAO.getByBoardId(id);
    }

JpaRepository:

public interface SimpleBoardDAO extends JpaRepository<SimpleBoard, Long> {
    SimpleBoard save(SimpleBoard simpleBoard);

    SimpleBoard getByBoardId(Long id);

I use H2-db, in order to create some test data I use data.sql. Then I use postman to call the endpoint and the response is like that:

{
    "boardId": 1,
    "boardName": "Test Board",
    "availableStatuses": [
        {
            "statusId": 3,
            "name": "Done",
            "sequence": 2
        },
        {
            "statusId": 2,
            "name": "In Progress",
            "sequence": 2
        },
        {
            "statusId": 1,
            "name": "To Do",
            "sequence": 1
        }
    ],
    "taskList": [
        {
            "taskId": 1,
            "taskName": "Create an To Do Application",
            "commentList": [
                {
                    "commentId": 1,
                    "content": "Nice job",
                    "creationDate": "2021-09-23T20:36:39.026"
                }
            ],
            "creationDate": "2021-09-23T20:36:39.024",
            "status": {
                "statusId": 1,
                "name": "To Do",
                "sequence": 1
            }
        }
    ]
}

I expect this endpoint to either return Board without associated Tasks and Comments or throw an Exception since I didn't use any graphs or JPQL Join Fetch methods to initialize these collections. As you can see the Board was fetched together with both of them. I know that this might happen in debugger mode or by calling some methods related to the collections, but I don't do anything like that.

The purpose of this project is to learn JPA and Hibernate. I know that the data model might be a mess and I need to overthink that once again. Hopefully, somebody can explain that to me. Thanks in advance!

P.S. I am aware of the fact that lazy fetching is a default one and putting FetchType on both sides of the association is redundant. I was just desperate :D

lacki
  • 1
  • 1
  • How did you determine that the fetch is happening eagerly? I rather suspect that you in fact have an N+1 problem here. You seem to be confusing _the database layer_ with _the Web API layer_. – chrylis -cautiouslyoptimistic- Sep 23 '21 at 20:33
  • Could you please elaborate on that? I thought that fetching is happening eagerly because calling getByBoardId() method results in querying also for the associated task and comments collections, isn't it exactly what is called eager fetching? – lacki Sep 24 '21 at 06:43
  • That is what eager fetching means, but you have no reason to claim that that is happening. Specifically, when you return `SimpleBoard` from your controller, the collection getters _are_ called when it is turned into JSON, triggering an inefficient lazy fetch. – chrylis -cautiouslyoptimistic- Sep 24 '21 at 07:06
  • Thank you, that makes a lot of sense. I wasn't aware of that actually happening when serializing an object. I'll now try to figure out why are these inefficient and how could I improve that! I have a lot to learn. Really appreciate your help. Cheers! – lacki Sep 24 '21 at 08:12

1 Answers1

0

According to the documentation @OneToMany

FetchType fetch (Optional) Whether the association should be lazily loaded or must be eagerly fetched. The EAGER strategy is a requirement on the persistence provider runtime that the associated entities must be eagerly fetched. The LAZY strategy is a hint to the persistence provider runtime.

This is possible a duplicate of JPA fetchType.Lazy is not working