본문 바로가기
백엔드/스프링

[Spring] JPA 실습 복습 자료

by AI읽어주는남자 2025. 11. 5.
반응형

JPA 실습 복습 자료

이 문서는 제공된 학습 노트와 실습 코드를 바탕으로 JPA의 핵심 개념과 실제 구현 방법을 정리한 복습 자료입니다.

1. JPA와 ORM 기본 개념

ORM (Object-Relational Mapping)

  • 정의: 객체 지향 프로그래밍의 객체와 관계형 데이터베이스의 테이블을 자동으로 매핑(연결)하는 기술입니다.
  • 목적: SQL 쿼리를 직접 작성하는 대신, 자바 객체를 통해 간접적으로 데이터베이스를 조작하여 개발 편의성을 높입니다.

Hibernate

  • 정의: 자바에서 ORM 기술을 구현한 대표적인 라이브러리입니다. JPA는 표준 명세(인터페이스)이고, Hibernate는 그 구현체 중 하나입니다.

JPA (Java Persistence API)

  • 정의: 자바 진영의 ORM 기술 표준 API입니다. 이를 통해 개발자는 특정 ORM 구현체에 종속되지 않고 코드를 작성할 수 있습니다.
  • 핵심 기능: 자바 객체(Entity)의 변경이 데이터베이스에 자동으로 반영되는 '영속성(Persistence)'을 관리합니다.

2. Entity (엔티티)

  • 정의: 데이터베이스 테이블과 직접 매핑되는 자바 클래스입니다. 엔티티 클래스는 테이블의 구조를, 엔티티 객체 하나는 테이블의 레코드(행) 하나를 의미합니다.
  • 주요 어노테이션:
    • @Entity: 해당 클래스가 DB 테이블과 매핑될 클래스임을 선언합니다.
    • @Table(name = "테이블명"): 매핑될 테이블의 이름을 지정합니다. (생략 시 클래스명을 따름)
    • @Id: 테이블의 기본 키(Primary Key)에 해당하는 필드를 지정합니다.
    • @GeneratedValue(strategy = GenerationType.IDENTITY): 기본 키 값을 데이터베이스가 자동으로 생성(auto_increment)하도록 설정합니다.
    • @Column: 필드와 테이블의 컬럼을 매핑하며, 세부 속성을 설정합니다.
      • nullable: false로 설정 시 NOT NULL 제약조건
      • unique: true로 설정 시 UNIQUE 제약조건
      • length: 문자열 길이
      • columnDefinition: TEXT, DATETIME 등 컬럼 타입을 직접 정의

실습 코드 예시: GoodsEntity.java

package example2.day02;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity // 해당 클래스를 데이터베이스 테이블처럼 매핑
@Table( name = "goods" ) // 테이블 이름 정의
@Data @Builder
@NoArgsConstructor
@AllArgsConstructor
public class GoodsEntity extends BaseTime { // BaseTime 상속 (아래 Auditing 참고)

    @Id                     // PK 필드
    @GeneratedValue( strategy = GenerationType.IDENTITY ) // auto_increment
    private int gno;        // 제품번호

    @Column( nullable = false , length = 100 ) // not null, varchar(100)
    private String gname;   // 제품명

    @Column( nullable = true ) // null 허용
    private int gprice;     // 제품가격

    @Column( columnDefinition = "varchar(100) default '제품설명' not null " ) // SQL 직접 작성
    private String gdesc;   // 제품설명

    // ... toDto() 메서드 ...
}

3. Repository (리포지토리)

  • 정의: 엔티티를 관리하고 데이터베이스에 대한 CRUD(Create, Read, Update, Delete) 작업을 처리하는 인터페이스입니다.
  • 설정 방법:
    1. 인터페이스를 생성합니다.
    2. JpaRepository<T, ID>를 상속받습니다.
      • T: 관리할 엔티티 클래스명 (GoodsEntity)
      • ID: 해당 엔티티의 PK 필드 타입 (Integer)
    3. @Repository 어노테이션을 추가하여 스프링 빈으로 등록합니다.

주요 기본 메서드

  • save(entity): 레코드 저장 (INSERT) 및 수정 (UPDATE)
  • findAll(): 모든 레코드 조회 (SELECT *)
  • findById(id): PK로 특정 레코드 1개 조회 (SELECT)
  • deleteById(id): PK로 특정 레코드 삭제 (DELETE)
  • existsById(id): PK에 해당하는 레코드 존재 여부 확인

실습 코드 예시: GoodsRepository.java

package example2.day02;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository // 빈 등록
// 지정한 엔티티들을 조작하는 인터페이스
public interface GoodsRepository
        extends JpaRepository<GoodsEntity, Integer> {
    // <조작할엔티티명, 엔티티PK자료형>
}

4. Service와 DTO (Data Transfer Object)

Service Layer

  • 역할: 컨트롤러와 리포지토리 사이에서 비즈니스 로직을 처리합니다. 트랜잭션 관리(@Transactional)가 주로 여기서 이루어집니다.
  • 엔티티 수정: JPA에서는 별도의 update 메서드가 없습니다. @Transactional이 적용된 메서드 내에서 엔티티를 조회한 후, 해당 객체의 setter를 호출하여 필드 값을 변경하면 트랜잭션이 커밋될 때 자동으로 UPDATE 쿼리가 실행됩니다. (이를 '더티 체킹'이라 합니다.)

DTO (Data Transfer Object)

  • 필요성: 엔티티는 DB 테이블과 직접 연결되어 있어 매우 중요한 객체입니다. API 응답/요청 등 외부 계층에 엔티티를 직접 노출하는 것은 보안 및 관리상 위험할 수 있습니다. DTO는 각 계층(특히 View-Controller) 간에 데이터를 안전하게 전달하기 위한 순수 데이터 객체입니다.
  • 변환:
    • DTO -> Entity: 클라이언트의 요청(JSON)을 DTO로 받은 후, Service에서 이를 Entity로 변환하여 DB에 저장합니다. (toEntity() 메서드)
    • Entity -> DTO: Service에서 DB로부터 조회한 Entity를 DTO로 변환하여 Controller에 전달하고, 클라이언트에게 응답합니다. (toDto() 메서드)

실습 코드 예시: GoodsServiceGoodsDto

GoodsDto.java (데이터 전달용 객체)

package example2.day02;

import lombok.Builder;
import lombok.Data;

@Data @Builder
public class GoodsDto {
    private int gno;
    private int gprice;
    private String create_date;
    private String update_date;
    private String gname;
    private String gdesc;

    // DTO를 Entity로 변환 (Controller -> Service)
    public GoodsEntity toEntity() {
        return GoodsEntity.builder()
                .gno( this.gno )
                .gname( this.gname )
                .gprice( this.gprice )
                .gdesc( this.gdesc )
                .build();
    }
}

GoodsEntity.java (Entity -> DTO 변환 메서드 추가)

// ... GoodsEntity 클래스 내부 ...
    // Entity를 DTO로 변환 (Service -> Controller)
    public GoodsDto toDto(){
        return GoodsDto.builder()
                .gno( this.gno )
                .gname( this.gname )
                .gprice( this.gprice )
                .gdesc( this.gdesc )
                .update_date( this.getUpdateDate().toString() )
                .create_date( this.getCreateDate().toString() )
                .build();
    }

GoodsService.java (비즈니스 로직)

package example2.day02;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
@Transactional
@RequiredArgsConstructor
public class GoodsService {

    private final GoodsRepository goodsRepository;

    // [1] 등록
    public GoodsDto goodsSave(GoodsDto goodsDto) {
        GoodsEntity entity = goodsDto.toEntity(); // DTO -> Entity
        GoodsEntity savedEntity = goodsRepository.save( entity );
        return savedEntity.toDto(); // Entity -> DTO
    }

    // [2] 전체조회
    public List<GoodsDto> goodsFindAll() {
        List<GoodsEntity> goodsEntityList = goodsRepository.findAll();
        return goodsEntityList.stream()
                .map(GoodsEntity::toDto) // 각 Entity를 DTO로 변환
                .collect(Collectors.toList());
    }

    // [4] 수정 (더티 체킹 활용)
    public GoodsDto goodsUpdate (GoodsDto goodsDto) {
        Optional<GoodsEntity> optional = goodsRepository.findById( goodsDto.getGno());
        if (optional.isPresent()) {
            GoodsEntity entity = optional.get(); // 영속성 컨텍스트에 포함된 엔티티
            // setter로 값만 변경하면, @Transactional에 의해 자동 UPDATE
            entity.setGname(goodsDto.getGname());
            entity.setGprice(goodsDto.getGprice());
            entity.setGdesc(goodsDto.getGdesc());
            return entity.toDto();
        }
        return goodsDto;
    }
}

5. JPA Auditing (감시)

  • 정의: 엔티티가 생성되거나 수정될 때, 생성일/수정일과 같은 필드를 자동으로 관리해주는 기능입니다.
  • 설정 방법:
    1. 메인 클래스 활성화: @EnableJpaAuditing 어노테이션을 추가합니다.
    2. 상속용 클래스 생성: @MappedSuperclass를 사용하여 공통 필드를 정의할 클래스를 만듭니다.
    3. 감시 리스너 추가: @EntityListeners(AuditingEntityListener.class)를 상속용 클래스에 추가합니다.
    4. 날짜 필드 매핑: 생성일 필드에 @CreatedDate, 수정일 필드에 @LastModifiedDate를 추가합니다.
    5. 엔티티에 상속: Auditing을 적용할 엔티티 클래스가 위에서 만든 상속용 클래스를 extends 하도록 합니다.

실습 코드 예시: BaseTime.java

package example2.day02;

import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;

@Getter
@MappedSuperclass // 엔티티 상속용 클래스
@EntityListeners(AuditingEntityListener.class) // JPA 감시 기능 활성화
public class BaseTime {

    @CreatedDate // 엔티티 생성 시 현재 날짜/시간 자동 주입
    private LocalDateTime createDate;

    @LastModifiedDate // 엔티티 수정 시 현재 날짜/시간 자동 주입
    private LocalDateTime updateDate;
}
  • 적용: public class GoodsEntity extends BaseTime { ... } 와 같이 상속받으면 createDateupdateDate 필드를 자동으로 관리받게 됩니다.

6. application.properties 주요 설정

JPA와 관련된 주요 설정입니다.

# JPA가 실행하는 SQL을 콘솔에 출력
spring.jpa.show-sql=true

# 콘솔에 출력되는 SQL을 보기 좋게 포맷팅
spring.jpa.properties.hibernate.format_sql=true

# 애플리케이션 실행 시 Entity와 DB 테이블 동기화 전략
# create: 기존 테이블 삭제 후 다시 생성 (테스트 환경)
# update: 변경된 스키마만 반영 (개발 환경)
# validate: Entity와 테이블이 다르면 경고 후 앱 실행 중단
# none: 아무것도 하지 않음 (운영 환경)
spring.jpa.hibernate.ddl-auto=update

# SQL의 '?' 파라미터에 바인딩되는 값을 로그로 출력
logging.level.org.hibernate.sql=DEBUG
logging.level.org.hibernate.orm.jdbc.bind=TRACE
반응형