
공통 질문
- Spring Data JPA와 순수 JPA의 차이점은 무엇인가요?
- JPA에서 Inheritance(상속) 매핑 전략 3가지를 설명해주세요.
- @Embedded와 @Embeddable의 역할과 사용 사례를 설명해주세요.
- Inheritance vs Embedded
- Locking 전략에 대해 설명해주세요.
- Soft Delete를 JPA에서 구현하려면 어떻게 해야 하나요?
Spring Data JPA와 순수 JPA의 차이점은 무엇인가요?

spring data jpa
jpa를 추상화한 spring module
interface 기반으로 동적 쿼리를 자동 생성
spring의 transaction, aop와 쉽게 통합 가능
장점
- 코드량 감소: jpaRepository를 상속받아 기본 메서드 사용 가능
- 동적 쿼리 생성을 위한 query 메서드 제공
단점
- 복잡한 쿼리는 queryDSL 외부 라이브러리 필요
pure jpa
jpa 표준 interface를 직접 사용하여 구현
entityManager를 통해 쿼리 작성 & 실행
장점
- 자유도가 높음
단점
- 직접적 코드 작성 필요
JPA에서 Inheritance(상속) 매핑 전략 3가지를 설명해주세요.
SINGLE_TABLE
모든 entity를 하나의 table에 저장
장점: 단일 테이블로 성능 우수
단점: 사용하지 않는 컬럼, 공간 낭비
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dtype")
public abstract class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
@Entity
public class Book extends Product {
private String author;
}
@Entity
public class Movie extends Product {
private String director;
}

TABLE_PER_CLASS
각 entity 별로 별도의 table 생성
장점: table이 entity와 일치
단점: join 불가능, 조회 시 비효율
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Product {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
}
@Entity
public class Book extends Product {
private String author;
}
@Entity
public class Movie extends Product {
private String director;
}

JOIN
각 entity를 별도의 table 저장 & join
장점: 중복 data 없음, 정규화 data 구조
단점: join으로 성능 저하
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
@Entity
public class Book extends Product {
private String author;
}
@Entity
public class Movie extends Product {
private String director;
}

@Embedded와 @Embeddable의 역할과 사용 사례를 설명해주세요.
- @Embeddable: 공통 도구
- @Embedded: 여기에 넣을게
@Embeddable
public class Address {
private String city;
private String street;
private String zipCode;
}
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@Embedded
private Address address;
}

Inheritance vs Embedded

Inheritance
테이블 간 상속 관계
상위 class의 속성, method를 공유
하위 class는 독립적 entity로 관리
사용 사례:
- 하위 클래스가 고유한 속성을 가지며, 이를 별도로 관리해야 하는 경우.
- 각 하위 클래스를 독립적으로 저장, 조회, 삭제 등의 작업이 필요한 경우.
Embedded
상위 Entity에 하위 entity가 포함되어 매핑됨
독립적인 entity가 아님
사용 사례:
- 공통 속성을 재사용하지만, 독립적으로 저장/조회할 필요가 없는 경우.
- 단순히 재사용 가능한 필드 그룹(주소, 금액 정보 등)을 구성할 때 적합.
Product, Book, Movie, Computer의 사례
(1) Inheritance로 구현
@Entity
@Inheritance(strategy = InheritanceType.JOINED) // JOINED 전략
public abstract class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
}
@Entity
public class Book extends Product {
private String author;
private int pages;
}
@Entity
public class Movie extends Product {
private String director;
private int duration;
}
@Entity
public class Computer extends Product {
private String cpu;
private int ram;
}
(1) Inheritance로 구현
결과
Product 테이블
Book 테이블
Movie 테이블
Computer 테이블
(2) @Embedded로 구현
@Entity
@Inheritance(strategy = InheritanceType.JOINED) // JOINED 전략
public abstract class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
}
@Entity
public class Book extends Product {
private String author;
private int pages;
}
@Entity
public class Movie extends Product {
private String director;
private int duration;
}
@Entity
public class Computer extends Product {
private String cpu;
private int ram;
}

Locking 전략에 대해 설명해주세요.
Optimistic Lock
작동 방식
- db lock 사용 안함
- @Version 필드로 데이터 변경 감지
- data 충돌 발생시, OptimisticLockException 발생 시킴 -> 처리
동작 원리
- 데이터를 읽을 때 **버전 정보(version)**도 함께 가져옵니다.
- 데이터를 업데이트할 때, 업데이트 요청의 버전 값과 데이터베이스에 저장된 버전 값을 비교합니다.
- 만약 두 버전 값이 같으면 업데이트를 수행하고, 버전 값을 증가시킵니다.
- 만약 버전 값이 다르면, 다른 트랜잭션에서 데이터를 변경한 것으로 판단하여 충돌을 발생시킵니다.
특징
- DB Lock 없음: 트랜잭션이 대기하지 않습니다.
- Queue 없음: 충돌 시 OptimisticLockException을 발생시키며, 응용 프로그램에서 이를 처리합니다.
- 성능이 우수하지만, 동시 쓰기 작업이 많은 환경에서는 충돌 가능성이 높아질 수 있습니다.
Pessimistic Lock
작동 방식
- JPA에서 Pessimistic Lock은 DB Lock을 활용
- 데이터베이스 레벨에서 **물리적인 잠금(DB Lock)**을 설정합니다.
동작 원리
1. 트랜잭션 A: 행 1에 대해 Pessimistic Lock 설정
- PESSIMISTIC_WRITE: 다른 트랜잭션은 해당 행에 대한 읽기와 쓰기를 모두 차단.
- PESSIMISTIC_READ: 다른 트랜잭션은 해당 행에 대해 쓰기를 차단하고 읽기는 허용.
2. 트랜잭션 B: 동일한 행 1에 접근하려고 시도.
- 트랜잭션 B는 데이터베이스의 Lock Queue에 대기.
- 대기 트랜잭션은 CPU를 점유하지 않고, Blocking Wait(Sleep) 상태로 들어갑니다.
- 트랜잭션 A가 작업을 완료하고 Commit 또는 Rollback 하면 잠금 해제.
3. 기존 트랜잭션이 완료되면, 잠금이 해제되고 대기 중인 트랜잭션이 실행됩니다.
- 트랜잭션 B는 잠금 해제 후 실행 재개.
Deadlock 처리
Pessimistic Lock은 데드락(Deadlock) 발생 가능성을 항상 염두에 둬야 합니다:
예시 (Deadlock 발생 상황):
- 트랜잭션 A: 행 1 잠금 → 행 2 대기.
- 트랜잭션 B: 행 2 잠금 → 행 1 대기.
- 두 트랜잭션이 서로를 기다리면서 교착 상태에 빠짐.
해결 방법:
- 트랜잭션 타임아웃 설정 (javax.persistence.lock.timeout).
- 잠금 순서를 명확히 정의.
Sleep (Blocking Wait)
- 대부분의 데이터베이스는 Blocking Wait(Sleep) 방식으로 잠금을 관리합니다.
- 만약 트랜잭션 A가 데이터를 잠근 상태에서, 트랜잭션 B가 동일한 데이터에 접근하려고 하면 B는 Sleep 상태로 전환됩니다.
- 트랜잭션 A가 작업을 완료하고 잠금을 해제하면, 트랜잭션 B가 깨어나서 실행을 재개합니다.

Optimistic Lock
@Version을 사용하여 충돌 방지
장점: 읽기 작업이 많은 경우 성능 우수
단점: update 많은 경우 실패 가능성 높음

@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToOne(mappedBy = "product", cascade = CascadeType.ALL)
private ProductStock stock;
// Getters and Setters
}
@Entity
public class ProductStock {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private int stockQuantity;
@Version // Optimistic Lock을 위한 버전 필드!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
private int version;
@OneToOne
@JoinColumn(name = "product_id")
private Product product;
@OneToMany(mappedBy = "stock", cascade = CascadeType.ALL)
private List<ProductStockHistory> histories = new ArrayList<>();
// Getters and Setters
}
@Entity
public class ProductStockHistory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "stock_id")
private ProductStock stock;
private int changedQuantity;
private String reason;
// Getters and Setters
}
@Transactional
public void decreaseStockWithOptimisticLock(EntityManager em, Long productStockId, int quantity) {
// 재고 조회
ProductStock stock = em.find(ProductStock.class, productStockId);
if (stock.getStockQuantity() < quantity) {
throw new IllegalArgumentException("재고 부족!");
}
// 재고 감소
stock.setStockQuantity(stock.getStockQuantity() - quantity);
// 변경 내역 저장
ProductStockHistory history = new ProductStockHistory();
history.setStock(stock);
history.setChangedQuantity(-quantity);
history.setReason("Order Fulfilled");
stock.getHistories().add(history);
// JPA의 @Version을 통해 트랜잭션 커밋 시점에 충돌 검출
em.persist(stock);
}
동시성 시뮬레이션
- Thread A: productStockId = 1, quantity = 10
- Thread B: productStockId = 1, quantity = 20
Thread A
- ProductStock 조회 (version = 1, stockQuantity = 100).
- stockQuantity 감소 (100 - 10 = 90).
- 커밋 시 version 증가 (version = 2).
Thread B
- ProductStock 조회 (version = 1, stockQuantity = 100).
- stockQuantity 감소 (100 - 20 = 80).
- 커밋 시 version 충돌 발생 → OptimisticLockException.

Pessimistic Lock
data에 잠금 설정, 동시성 문제 방지
장점: 동시 update 충돌 방지
단점: 잠금으로 인해 성능 저하

@Transactional
public void decreaseStockWithPessimisticLock(EntityManager em, Long productStockId, int quantity) {
// 재고 조회 + Pessimistic Lock 설정!!!!!!!!!!!!!!!!!!
ProductStock stock = em.find(ProductStock.class, productStockId, LockModeType.PESSIMISTIC_WRITE);
if (stock.getStockQuantity() < quantity) {
throw new IllegalArgumentException("재고 부족!");
}
// 재고 감소
stock.setStockQuantity(stock.getStockQuantity() - quantity);
// 변경 내역 저장
ProductStockHistory history = new ProductStockHistory();
history.setStock(stock);
history.setChangedQuantity(-quantity);
history.setReason("Order Fulfilled");
stock.getHistories().add(history);
em.persist(stock);
}
동시성 시뮬레이션
- Thread A: productStockId = 1, quantity = 10
- Thread B: productStockId = 1, quantity = 20
Thread A
- ProductStock 조회 (stockQuantity = 100) 및 Pessimistic Lock 설정.
- stockQuantity 감소 (100 - 10 = 90).
- 커밋 후 잠금 해제.
Thread B
- ProductStock 조회 시 잠금 대기 상태.
- Thread A가 작업 완료 후 잠금 해제.
- stockQuantity = 90에서 감소 실행 (90 - 20 = 70).

Soft Delete를 JPA에서 구현하려면 어떻게 해야 하나요?
논리적 삭제
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private boolean isDeleted;
}
// 삭제 메서드
public void softDelete(EntityManager em, Long userId) {
User user = em.find(User.class, userId);
user.setDeleted(true);
em.persist(user);
}
'학습 기록 (Learning Logs) > CS Study' 카테고리의 다른 글
Fetch Join (0) | 2024.12.12 |
---|---|
JPA-troubleShooting (0) | 2024.12.12 |
JPA-optimized-performance (0) | 2024.12.12 |
JPA-features (0) | 2024.12.12 |
JPA-mapping (0) | 2024.12.12 |