Spring Boot
강의

#12 - JPA 실무 가이드 1부 — 입문자 기준 쉽게 이해하기(30분 JPA 정복)

중년개발자
중년개발자

@loxo

약 8시간 전

2

JPA 실무 가이드 1부 — 입문자 기준 쉽게 이해하기

SQL을 직접 작성하던 시절, 개발자는 항상 DB 구조에 맞춰 움직여야 했습니다. 그래서 JPA는 복잡하고 어려운 기술처럼 느껴질 수 있습니다. 하지만 실무에서 제대로 사용해보면, JPA는 객체 중심으로 개발을 단순화해주는 도구입니다. 이 문서는 불필요한 이론을 걷어내고, 실무에 꼭 필요한 핵심만 정리한 30분 요약 가이드입니다.


0. JPA가 무엇인가요?

JPA (Java Persistence API, 자바 영속성 API)는

자바 객체(Object)를 데이터베이스(DB)에 저장하고 관리하는 기술입니다.

조금 더 쉽게 말하면,

  • 우리가 객체(Object)를 만들고
  • 값을 바꾸면
  • JPA가 알아서 SQL을 만들어 DB에 반영해 줍니다.

여기서 중요한 개념이 바로

  • ORM (Object Relational Mapping, 객체-관계 매핑)
    입니다.

객체(Object)와 테이블(Table)을 연결해주는 기술이 ORM입니다.


1. 엔티티(Entity, 테이블과 매핑되는 클래스)

엔티티(Entity)는

데이터베이스 테이블과 1:1로 연결되는 자바 클래스입니다.

예시:

java
@Entity @Getter @Table(name = "table_name") // 1. 테이블명 소문자 명시 @NoArgsConstructor(access = AccessLevel.PROTECTED) // 2. 기본 생성자 막기 public class DefaultEntity extends BaseEntity { // 3. BaseEntity 상속 @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) @Column(name = "id") private Long id; @Column(nullable = false) private String name; // 4. @Builder: 생성자 대신 빌더 사용 @Builder public DefaultEntity(String name) { this.name = name; } // 5. 비즈니스 로직: Setter 대신 메서드 public void changeName(String name) { this.name = name; } }

여기서 중요한 개념들:

  • @Entity → 이 클래스는 테이블과 연결됨
  • @Id → 기본 키(Primary Key, PK)
  • @GeneratedValue → PK 자동 생성 전략

1-1. 왜 @Setter를 지양(권장하지 않음)할까요?

가장 많이 실수 하는것이 바로 @Setter입니다.
하지만 엔티티(Entity)에 무분별하게 @Setter를 열어두면 문제가 생깁니다.

예를 들어:

java
order.setPrice(1000);

이 코드는 단순히 "값을 바꿨다"는 것만 보여줍니다.

나중에 이런 질문이 생깁니다.

  • 언제 가격이 바뀌었지?
  • 왜 바뀌었지?
  • 할인인가?
  • 환불인가?

의도가 코드에 드러나지 않습니다.

❌ 좋지 않은 예

java
order.setStatus(CANCEL); // 그냥 값 변경

⭕ 좋은 예

java
order.cancel(); // "주문 취소"라는 행위가 명확함

이처럼

엔티티는 데이터 덩어리(Data Holder)가 아니라
"행위(Behavior)를 가진 객체"로 설계해야 합니다.

이 방식의 장점:

  • 비즈니스 규칙을 메서드 안에 넣을 수 있음
  • 잘못된 상태 변경을 막을 수 있음
  • Dirty Checking(변경 감지) 의도가 명확해짐

따라서 실무에서는:

  • 엔티티에는 무분별한 @Setter 사용을 피하고
  • 의미 있는 메서드로 상태를 변경하도록 설계합니다.

2. 기본 키 전략 (Primary Key Strategy, 기본 키 생성 방식)

PostgreSQL에서는 보통 두 가지 전략을 많이 사용합니다.

2-1. SEQUENCE (시퀀스 기반 생성)

java
@GeneratedValue(strategy = GenerationType.SEQUENCE)

특징:

  • DB의 시퀀스(Sequence, 번호 생성기)를 사용
  • 여러 건을 한 번에 저장할 때(batch insert, 일괄 저장) 성능이 좋음
  • 실무에서 가장 많이 사용됨

2-2. IDENTITY (자동 증가 컬럼)

java
@GeneratedValue(strategy = GenerationType.IDENTITY)

특징:

  • DB가 insert 할 때 자동으로 번호 생성
  • 단순하지만 batch insert가 어려움

📌 정리

PostgreSQL 실무에서는 보통

SEQUENCE 전략을 더 많이 사용합니다.


3. 숫자 PK는 보안상 위험한가요?

많이 오해하는 부분입니다.

문제는:

  • PK가 숫자라서가 아니라
  • 그 숫자를 외부 API에 그대로 노출하기 때문입니다.

해결 방법:

  • 내부 PK → Long + SEQUENCE
  • 외부 노출 ID → UUID (Universally Unique Identifier, 범용 고유 식별자)

즉, 보안은 전략 문제가 아니라 설계 문제입니다.


4. UUID (범용 고유 식별자)

Hibernate 6에서는

java
@UuidGenerator(style = UuidGenerator.Style.TIME)

처럼 시간 기반 UUID (time-ordered UUID, 시간 순 정렬 UUID)를 지원합니다.

UUID v4는 완전 랜덤(Random)이라

  • 인덱스(Index, 색인)
  • B-Tree 구조

에서 성능이 조금 떨어질 수 있습니다.

그래서 최근에는 시간 순 UUID를 더 선호합니다.


5. Dirty Checking (변경 감지)

JPA의 가장 중요한 개념입니다.

Dirty Checking(변경 감지)이란:

객체의 값이 바뀌면 자동으로 UPDATE SQL을 생성하는 기능

예시:

java
@Transactional public void changePrice(Long id) { Order order = repository.findById(id).orElseThrow(); order.changePrice(1000); }
  • 트랜잭션(Transaction, 작업 단위) 시작
  • 엔티티(Entity) 조회
  • 값 변경
  • commit 시점에 자동 UPDATE 실행

이때 내부적으로

  • 영속성 컨텍스트(Persistence Context, 엔티티 관리 공간)
  • 스냅샷(Snapshot, 초기 상태 저장)

을 사용해 변경 여부를 판단합니다.


6. save()는 언제 필요할까요?

Spring Data JPA에서

java
repository.save(entity);

동작 방식:

  • id == null → persist (신규 저장)
  • id != null → merge (병합)

💡 INSERT는 왜 save()를 해야 하나요?

Dirty Checking은 "이미 있는 것(Managed)"이 "바뀌었는지(Dirty)" 검사하는 기능입니다.
새로 만든 객체(new Order())는 아직 JPA가 모르는 녀석(Transient)입니다.
그래서 repository.save()를 호출해서 "이것도 관리해줘(Persist)"라고 알려줘야 합니다.

📌 핵심 정리

  • 새로 만든 객체는 save() 필요
  • 이미 조회한 객체는 save() 없이도 변경 반영

7. Auditing (자동 시간 기록)

생성일(createdAt)과 수정일(updatedAt)을 자동으로 넣어주는 기능입니다.

7-1. 공통 부모 클래스(BaseEntity) 만들기

java
@Getter @MappedSuperclass // "나는 테이블은 아니고, 자식들에게 필드만 물려줄게" @EntityListeners(AuditingEntityListener.class) // "누가 건드리는지 감시해줘" public abstract class BaseEntity { @CreatedDate // 생성될 때 시간 자동 저장 @Column(updatable = false) // 수정 불가 (태어난 시간은 안 바뀌니까) private LocalDateTime createdAt; @LastModifiedDate // 변경될 때마다 시간 자동 갱신 (Dirty Checking 시점) private LocalDateTime updatedAt; }

설명:

  • @MappedSuperclass → 테이블로 생성되지 않고, 필드만 상속됨
  • @EntityListeners → Auditing 기능 활성화
  • @CreatedDate → INSERT 시 자동 입력
  • @LastModifiedDate → UPDATE 시 자동 갱신

7-2. 엔티티에서 상속받기

java
@Entity public class Order extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private Long id; private int price; }

이제 Order를 저장하거나 수정하면
createdAt / updatedAt이 자동으로 들어갑니다.


7-3. 반드시 필요한 설정

① 메인 애플리케이션 클래스

java
@SpringBootApplication @EnableJpaAuditing public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }

② 의존성 확인 (Gradle 예시)

gradle
dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' }

7-4. 동작 예시

java
@Transactional public void createOrder() { Order order = new Order(); repository.save(order); }

저장 후 DB를 보면:

  • created_at → 현재 시간 자동 입력
  • updated_at → 현재 시간 자동 입력

수정하면:

  • updated_at → 자동 갱신

이것이 Auditing(자동 기록) 기능입니다.


8. @Transactional (트랜잭션 관리)

java
@Transactional

의 의미:

이 메서드 안의 작업은 하나의 묶음으로 처리된다.

기본 동작:

  • RuntimeException → rollback (되돌림)
  • Checked Exception → 기본적으로 rollback 안 됨

9. 입문자 기준 최종 정리

실무에서 가장 안전한 기본 구조:

  • PK → Long + SEQUENCE, UUID
  • 외부 노출 ID → UUID 별도 사용
  • 엔티티는 setter 대신 의미 있는 메서드 사용
  • 트랜잭션은 Service 레이어에서 관리
  • Dirty Checking 이해가 가장 중요

📚 참고 공식 문서 (Reference URLs)

  1. Jakarta Persistence (JPA) Specification
    https://jakarta.ee/specifications/persistence/

  2. Hibernate ORM 공식 문서
    https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html

  3. Spring Data JPA Reference
    https://docs.spring.io/spring-data/jpa/reference/

  4. PostgreSQL Documentation
    https://www.postgresql.org/docs/

  5. UUID RFC (RFC 4122)
    https://www.rfc-editor.org/rfc/rfc4122

#JPA#Spring Boot#Hibernate#ORM#PostgreSQL

댓글 0

Ctrl + Enter를 눌러 등록할 수 있습니다
※ AI 다듬기는 내용을 정제하는 보조 기능이며, 최종 내용은 사용자가 확인해야 합니다.