공부/PHP

PHP에서 Closure로 Lazy loading 구현하기

2020. 5. 29. 16:02

spring jpa를 사용하면 간단하게 db에 있는 데이터를 객체로 받는다.

연관된 테이블도 어노테이션 하나만으로 JOIN이 가능하다.

또한 fetchType을 이용하여 즉시 조회할지 지연 조회할지 설정도 가능하다.

 

게시판 테이블

board_id name
1 공지 게시판
2 자유 게시판

게시글 테이블

post_id board_id title content
1 1 공지글 공지글 입니다.
2 1 공지사항 공지사항 입니다
3 2 자유글 자유글 입니다

위 테이블을 예로 들면

게시판 객체에는 멤버 변수로 게시판 아이디, 게시판 이름, 게시글(1:N)이 있을 것이다.

그래서 jpa를 사용하면 게시판 한번 조회 시 1:N으로 연결된 게시글도 모두 가져올 수 있다

하지만 여기서 게시판만 조회해서 사용하려는 경우도 게시글을 모두 가져오기 때문에 불필요한 데이터를 가져오는 낭비가 발생한다

이를 해결하기 위해 lazy loading을 사용한다

lazy loading이란, 게시글 속성을 lazy loading 처리할 경우

게시판 조회 시 게시판만 조회가 되고 게시판에서 board.getPosts()와 같이 게시판의 멤버 변수인 게시글을 가져올 경우 그때 게시글을 가져오는 쿼리가 발생한다.

jpa에서는 단순히 어노테이션만으로 lazy loading을 적용할 수 있다

 

 

하지만 이것은 어노테이션만으로 많은 작업들을 가능하게 하는 스프링일 때 가능한 것이고 현재 회사에서 사용하는 php에는 적용되지 않는다.

그래서 php로도 lazy loading을 가능하도록 하는 방법을 찾았고, 그 방법을 적용시켜본 결과 정상적으로 잘 작동하기에 이 글을 남긴다.

방법은 클로저를 이용하는 것이다. 클로저를 lazy loading을 위해 처음 사용해 보았다.

 

아래는 간단하게 예제를 작성한 것이다. (머릿속으로 재 구현한 것이므로 오타가 있을 수 있음)

<?php

// domain layer
class Board
{
    private $id;
    private $name;
    /**
     * @var array<posts>
     */
    private $posts;
    /**
     * @var Closure
     */
    private $postReference;
    
    // 생성자 ...
    
    public function setPostReference(Closure $reference) {
    	$this->postReference = $reference;
    }
    public function getPosts(): array {
    	if( ! isset($this->posts)) {
        	$reference = $this->postReference;
            $this->posts = $reference();
        }
        return $this->posts;
    }
}

// persistence layer
class BoardRepository
{
	public function find(int $boardId) {
    
    	$boardData = $this->db->query('SELECT * FROM baord WHERE board_id = ' . $boardId);
        $board = new Board($boardData);
        
        $postReference = function(int $boardId) {
        	$postsData = $this->db->query('SELECT * FROM post WHERE board_id = ' . $boardId);
            $posts = array_map(function($row) {
           		return new Post($row)
           	}, $postsData);
        }
        $board->setPostReference($postReference);
        
        return $board;
    }
}


// service layer
class BoardService
{
	public function getPosts(int $boardId) {
    	// (SELECT * FROM BOARD WEHRE board_id = $boardId;) 쿼리만 실행됨
    	$board = $this->boardRepository->find($boardId);
        
        // ... 비지니스 로직
        
        // 이때 게시글 가져오는 쿼리가 실행됨 ('SELECT * FROM post WHERE board_id = ' . $boardId;)
        // $postReference 함수가 실행되면서 $board의 멤버 변수로 posts가 생김
        $posts = $board->getPosts();
        // 또 한번 실행시 쿼리가 실행되지 않고 멤버변수만 반환됨
        $posts2 = $board->getPosts();
    }
}

 

이처럼 lazy loading을 활용할 수 있다. 그러나 lazy loading은 N + 1 문제를 발생시키기 때문에 주의하여 사용해야 한다고 한다.

orm을 사용 시 성능에 문제가 발생할 수 있다는 것이 이러한 부분인 것 같다.

 

스프링에서는 어노테이션으로 간단하게 하는 작업을 직접 구현하여 만들어본 결과 번거롭지만 꽤 만족스러웠다.

추후에 이 글을 보았을 때 쉽게 이해하길 바라며 이 글을 마친다.

반응형