Lazy Loading

2024. 7. 31. 16:10Spring Boot

이전에 JPA N+1 문제에 관련된 글을 쓴 적이 있다.

후에 좀 더 알아보니 아예 Fetch EAGER을 쓰지 말고 Fetch LAZY를 쓰는 편이 더 좋다고 해서 

그에 대해 공부하고 구현해 보았다.

 

우선 EAGER과 LAZY의 차이점을 알아보자

 

package com.example.demo.mySQL;


import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.ArrayList;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonIgnore;
@Entity
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Table(schema = "sql_entity")
public class sqlEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private String title;

    @Column
    private String data;

    @OneToMany(mappedBy = "parent", cascade = CascadeType.REMOVE, fetch = FetchType.LAZY)
    private List<sqlChild> childs = new ArrayList<>();

    sqlEntity(String title, String data) {
        this.title = title;
        this.data = data;
    }
}

 

OneToMany 혹인 ManyToOne의 관계에서

FetchType을 EAGER로 설정한 경우 

위와 같은 @OneToMany에서는 childs를 전부 같이 불려온다

 

하지만 LAZY로 설정한 경우 실제 값을 불러오는 게 아니라 Proxy 객체를 넣어둔다

 

즉 child가 바로 필요한 경우면 EAGER로 두어도 괜찮으나
아닌 경우에는 불필요한 DB와 I/O 작업이 일어난다는 의미이다.

 

그래서 기본적으로는 EAGER로 설정되어 있으나 LAZY를 권장한다.

 

그런데 LAZY로 설정하면 기존에 사용하던 코드에서 오류가 난다.

 

 

 

could not initialize proxy - no Session

 

이는 기존과 달리 child의 값이 초기화가 되어있지 않고 proxy가 들어 있어서 생기는 문제이다.

 

해결 방안은 여러가지가 있는데

 

1. Fetch Join을 사용하여 관련 엔티티 미리 로딩하기

 

2. DTO 사용하기

 

3. 서비스 계층에서 초기화하기

 

4. Hibernate.initialize() 사용하기

 

가 있다

 

이중 @Transactional을 이용해서 서비스 계층에서 초기화하는 방법 + DTO 사용하기로 문제를 해결하였다

 

 

 

 

우선 DTO만을 사용하는 방법이다.

 

package com.example.demo.mySQL;

import java.util.List;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class entityDTO {

    private Long id;

    private String title;

    private String data;

    entityDTO(sqlEntity entity) {
        this.title = entity.getTitle();
        this.data = entity.getData();
        this.id = entity.getId();
    }
}
    public List<sqlEntity> all_data() {
        return repo.findAll();
    }
    @RequestMapping(method = RequestMethod.GET)
    public String all_data2(Model model) {
        List<sqlEntity> list = service.all_data();
        List<entityDTO> dtoList = new ArrayList<>();
        for(sqlEntity entity : list) {
            dtoList.add(new entityDTO(entity));
        }
        model.addAttribute("testList", dtoList);
        return "db/mySQLBoard";
    }

 

위 케이스는 child의 데이터가 필요 없으니 child가 없는 DTO를 만들어서 뿌려주는 방식으로 문제를 해결했다.

 

 

 

 

 

 

 

 

 

이번에는 @Transactional을 이용하는 방법이다.

해당 케이스는 child 데이터도 필요해서 서비스 단에서 getChilds를 이용해 일일이 child를 초기화주었다.

package com.example.demo.mySQL;

import java.util.ArrayList;
import java.util.List;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class childDTO {

    private Long id;

    private String title;

    private String data;

    private List<sqlChild> childs = new ArrayList<>();

    public childDTO(sqlEntity entity) {
        this.id = entity.getId();
        this.data = entity.getData();
        this.title = entity.getTitle();

        for(sqlChild child : entity.getChilds()) {
            childs.add(child);
        }
    }
}
    @Transactional(transactionManager = "myTran")
    public childDTO get_data(Long id) {
        sqlEntity entity =  repo.findById(id).get();
        childDTO dto = new childDTO(entity);
        return dto;
    }
    @RequestMapping(path = "/{id}", method = RequestMethod.GET)
    public String detail_data(@PathVariable Long id, Model model) {
        
        childDTO dto = service.get_data(id);
        model.addAttribute("detail", dto);
        model.addAttribute("childList", dto.getChilds());
        return "db/mySQLdetail";
    }

 

이는 @Transactional을 붙여서 entitiy를 영속성 컨텍스트 저장하여 child를 지연 로딩한 것이다.

'Spring Boot' 카테고리의 다른 글

OSIV  (0) 2024.08.03
@Transactional 과 영속성  (0) 2024.08.02
Spring AOP  (0) 2024.07.10
여러 데이터 베이스 사용 시 Transaction  (0) 2024.07.10
R2DBC  (0) 2024.06.17