최원종의 개발 블로그

V2(PersistentContext) 게시글 작성 - PersistenceContext 활용 본문

Spring boot 입문

V2(PersistentContext) 게시글 작성 - PersistenceContext 활용

chl6698 2026. 5. 7. 13:50

Persistence Context(영속성 컨텍스트)

영속성 컨텍스트
엔티티(객체)를 영구 저장하고 관리하는 논리적인 메모리 영역. 
애플리케이션과 데이터베이스(DB) 사이에서 중계자 역할

영속상태
영속성 컨텍스트에 의해 관리되는 상태, 데이터베이스와의 동기화가 이루어짐

 

1차 캐시 (Memory Cache): DB에 가기 전에 메모리에서 먼저 찾습니다. 
똑같은 데이터를 두 번 조회하면 두 번째는 DB에 물어보지 않고 메모리에서 꺼내옵니다.

동일성 보장 (Identity): 같은 ID로 조회한 두 객체는 자바의 == 비교에서 항상 true입니다. 


쓰기 지연 (Write-Behind): save를 한다고 바로 DB에 쿼리를 쏘지 않습니다. 
쿼리를 모아뒀다가 트랜잭션이 끝나는(Commit) 순간 한꺼번에 보냅니다. 
(택배를 하나씩 안 보내고 트럭에 모아서 한 번에 보내는 것과 같음.)

변경 감지 (Dirty Checking): 객체의 값만 바꿔두면, 트랜잭션이 끝날 때
JPA가 처음과 값이 다른 걸 확인 하고 알아서 UPDATE 쿼리를 만들어 날려줍니다.

 

JPA를 선택하는 이유

1. **영속성 컨텍스트**: 자동 상태 관리, 변경 감지, 1차 캐시
2. **객체지향적 개발**: 엔티티 중심의 비즈니스 로직
3. **생산성**: 반복적인 CRUD 코드 자동화
4. **표준화**: JPA 표준으로 벤더 독립적
5. **연관관계 관리**: 객체 간 관계를 자연스럽게 표현

Board.java 코드

package com.tenco.blog.board;

import com.tenco.blog.util.MyDateUtil;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;

import java.sql.Timestamp;

@Data // get,set, toString ..
// @Entity - JPA가 이 클래스를 데이터베이스 테이블과 매핑하는 객체로 인식하게 설정
// 즉, 이 어노테이션이 있어야 JPA가 관리 함
@Entity
@Table(name = "board_tb")
@NoArgsConstructor
@AllArgsConstructor // 전체 멤벼 번수를 넣을 수 있는 생성자.
@Builder
public class Board {

    // @id : 이 필드가 기본키임을 설정 함
    @Id
    // IDENTITY 전략: 데이터베이스게 기본 AUTO_INCREMENT 기능 사용
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String username;
    private String title;
    private String content;

    // @CreationTimestamp : 하이버네이트가 제공하는 어노테이션
    // 특정 하나의 엔티티가 저장이 될 때 현재 시간을 자동으로 저장해 설정
    // now() 명시할 필요 없음
    // pc --> db (자동 날짜 주입)
    @CreationTimestamp
    private Timestamp createdAt;

    // createdAt -> 포멧 하는 메서드 만들어 보기
    public String getTime() {
        return MyDateUtil.timestampFormat(createdAt);
    }

}

BoardPersistRepository.java코드

package com.tenco.blog.board;

import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

@Repository // IoC
@RequiredArgsConstructor // DI 처리 됨
public class BoardPersistRepository {

    // JPA 핵심 인터페이스
    // 영속성 컨텍스트를 관리하고 엔티티의 생명주기를 제어
    // @Autowired // DI
    private final EntityManager em; // final 사용하면 성능 개선이 조금 됨

    // 의존 주입 (외부에서 생성되어 있는 객체의 주소값을 주입 받다)
//    public BoardPersistRepository(EntityManager em) {
//        this.em = em;
//    }

    // 게시글 저장
    @Transactional
    public Board save(Board board) {
        // 1. 매개 변수로 받은 board는 비영속상태
        //    -- 아직 영속성 컨텍스트에 관리 되고 있지 않는 상태
        //    -- 데이터베이스과 연관 없는 수순 JAVA 객체 일 뿐 아직은..

        //em.createNativeQuery("insert into board_tb .... ) x
        em.persist(board); // insert 처리 완료
        // 2. 이 board 객체를 영속성 컨텍스트에 넣어 둠 (SQL 저장소에 등록)
        //     -- 영속성 컨텍스트에 들어가더라도 아직 DB 실제 insert한 상태는 아님

        // 3. 트랜잭션 커밋시점에 실제 DB에 접근해서 insert 구문이 수행이 된다.

        // 4. board 객체의 id 변수값을 1차 캐쉬에 map 구조로 보관 되어 짐.
        //    1차 캐쉬에 들어간 이제 영속상태로 변경된 Object 리턴 한다.
        return board;
    }
}

board/BoardRequest.java코드

package com.tenco.blog.board;

import lombok.Builder;
import lombok.Data;

// 요청 데이터를 담는 DTO 클래스
// 컨트롤러.. 비즈니스 .. 데이터 계층 사이에서 데이터 전송 역할 객체
public class BoardRequest {

    @Data
    @Builder
    public static class SaveDTO {
        private String username;
        private String title;
        private String content;

        // 편의 기능 설계 가능
        // DTO 에서 Entity로 변환해주는 편의 메서드
        public Board toEntity() {
            return Board.builder()
                    .username(username)
                    .title(title)
                    .content(content)
                    .build();
        }
    }
}

BoardController코드

    /**
     * 게시글 작성 기능 요청
     * @return 페이지 반환
     * 주소설계 : http://localhost:8080/board/save-form
     */
    @PostMapping("/board/save")
    // 사용자 요청 -> HTTP 요청 메시지(Post)
    public String saveProc(BoardRequest.SaveDTO saveDTO) {
        Board board = saveDTO.toEntity();
        boardPersistRepository.save(board);
        return "redirect:/";
    }