KT에이블스쿨 7기

[KT AIVLE School 7기] Spring Framework(4)

CodeForWelfare 2025. 6. 24. 11:50

 

1. JPA란?

JPA(Java Persistence API)는 자바 객체와 관계형 데이터베이스 간의 매핑을 위한 자바 표준 ORM(Object Relational Mapping) 프레임워크입니다. SQL 대신 객체지향적인 방식으로 데이터를 관리할 수 있게 해주며, JPQL이라는 객체 중심 질의 언어를 사용합니다.

JPA는 아래의 대표적인 구현체로 사용됩니다:

  • Hibernate (가장 널리 사용됨)
  • EclipseLink
  • OpenJPA 등

2. JPA의 주요 이점

  • 객체지향적 데이터 모델링 가능 (Entity 기반 설계)
  • 쿼리 작성 최소화 → 생산성 향상
  • SQL 추상화로 DB 독립성 확보
  • 1차 캐시를 통한 성능 향상 (영속성 컨텍스트)

3. 엔티티 클래스 정의

JPA에서 Entity는 DB의 테이블과 매핑되는 자바 클래스입니다.

@Entity
@Table(name = "members")
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    private int age;
    // getter, setter 생략
}
  • @Entity: 클래스가 JPA 엔티티임을 표시
  • @Id: 기본 키(PK) 지정
  • @GeneratedValue: 기본 키 생성 전략
  • @Column: 칼럼 세부 설정

4. 영속성 컨텍스트 (Persistence Context)

JPA는 EntityManager를 통해 엔티티 객체를 관리합니다. 이 객체들은 영속성 컨텍스트라는 저장소에 의해 관리됩니다.

생명주기 단계

상태 설명

비영속 영속성 컨텍스트에 저장되기 전 상태
영속 영속성 컨텍스트에 저장된 상태
준영속 영속성 컨텍스트에서 분리된 상태
삭제 DB에서 삭제 예정인 상태

예시

Member member = new Member("홍길동"); // 비영속
em.persist(member); // 영속
em.detach(member); // 준영속
em.remove(member); // 삭제

5. Repository - Spring Data JPA

스프링에서는 JPA를 쉽게 사용할 수 있도록 Spring Data JPA를 제공합니다. 인터페이스만 정의하면, 기본 CRUD 기능을 자동으로 생성해줍니다.

public interface MemberRepository extends JpaRepository<Member, Long> {
    List<Member> findByName(String name);
}
  • JpaRepository를 상속하면 CRUD, 페이징, 정렬 기능이 내장됩니다.
  • 메서드 이름을 기반으로 자동으로 쿼리를 생성합니다.

사용자 정의 쿼리 예시

@Query("SELECT m FROM Member m WHERE m.age > :age")
List<Member> findOlderThan(@Param("age") int age);

6. 연관관계 매핑 (관계형 DB 모델링)

객체 간의 관계를 명시적으로 맺을 수 있습니다. (1:1, 1:N, N:M 등)

예시: 회원과 주문 (1:N)

@Entity
public class Member {
    @OneToMany(mappedBy = "member")
    private List<Order> orders = new ArrayList<>();
}

@Entity
public class Order {
    @ManyToOne
    @JoinColumn(name = "member_id")
    private Member member;
}
  • @OneToMany, @ManyToOne: 관계 방향 설정
  • mappedBy: 주인이 아님을 명시 (읽기 전용)
  • @JoinColumn: 외래 키(FK) 지정

7. Fetch 전략

  • EAGER: 연관된 객체를 즉시 조회 (기본값: @ManyToOne)
  • LAZY: 실제 사용할 때 조회 (지연 로딩)
@ManyToOne(fetch = FetchType.LAZY)
private Member member;

실제 운영에서는 LAZY가 권장되며, 필요시 JOIN FETCH를 사용해 함께 로딩합니다.


8. 영속성 전이 (Cascade)와 고아 객체 제거

Cascade

엔티티 간의 관계에 따라 연쇄적으로 영속/삭제 처리가 되도록 설정합니다.

@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
private List<Order> orders;

Orphan Removal

부모 엔티티에서 제거된 자식 엔티티를 자동으로 삭제합니다.

@OneToMany(mappedBy = "member", orphanRemoval = true)
private List<Order> orders;

9. Auditing (자동 시간 관리)

Spring Data JPA에서는 @CreatedDate, @LastModifiedDate 어노테이션을 통해 엔티티의 생성일자 및 수정일자를 자동으로 관리할 수 있습니다.

사용 예시

@EntityListeners(AuditingEntityListener.class)
public class BaseTimeEntity {
    @CreatedDate
    private LocalDateTime createdDate;

    @LastModifiedDate
    private LocalDateTime modifiedDate;
}
  • @EnableJpaAuditing 설정 필요 (설정 클래스에서)

10. 복합 키 매핑 전략

1) @EmbeddedId 방식

@Embeddable
public class OrderId implements Serializable {
    private Long memberId;
    private Long productId;
}

@Entity
public class Order {
    @EmbeddedId
    private OrderId id;
}

2) @IdClass 방식

@Entity
@IdClass(OrderId.class)
public class Order {
    @Id private Long memberId;
    @Id private Long productId;
}

11. 상속 매핑

JPA는 객체지향 상속 구조를 테이블에 매핑할 수 있도록 지원합니다.

주요 전략

  • SINGLE_TABLE: 하나의 테이블에 모든 자식 클래스 정보를 함께 저장
  • JOINED: 부모 테이블과 자식 테이블을 조인해서 사용
  • TABLE_PER_CLASS: 자식 클래스마다 별도의 테이블 생성
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "dtype")
public abstract class Item {
    @Id @GeneratedValue
    private Long id;
    private String name;
}

@Entity
public class Book extends Item {
    private String author;
}

12. QueryDSL 기본 설정과 사용

QueryDSL은 타입 세이프한 쿼리 작성을 지원하는 라이브러리입니다.

Gradle 설정

dependencies {
    implementation 'com.querydsl:querydsl-jpa'
    annotationProcessor 'com.querydsl:querydsl-apt'
}

기본 사용

JPAQueryFactory query = new JPAQueryFactory(em);
QMember m = QMember.member;

List<Member> result = query.selectFrom(m)
                            .where(m.age.gt(20))
                            .fetch();

13. BooleanBuilder를 활용한 동적 쿼리

사용자의 입력에 따라 조건을 유동적으로 조합할 수 있습니다.

BooleanBuilder builder = new BooleanBuilder();
if (name != null) {
    builder.and(member.name.eq(name));
}
if (age != null) {
    builder.and(member.age.gt(age));
}
List<Member> result = queryFactory.selectFrom(member)
                                  .where(builder)
                                  .fetch();

14. Specification (복잡한 조건 조합)

Spring Data JPA에서 제공하는 JpaSpecificationExecutor를 사용해 동적 조건 검색을 지원합니다.

public class MemberSpec {
    public static Specification<Member> nameLike(String name) {
        return (root, query, cb) -> cb.like(root.get("name"), "%" + name + "%");
    }
}

15. JPA 테스트 전략

주요 어노테이션

  • @DataJpaTest: JPA 테스트에 필요한 설정만 로드
  • @Transactional: 테스트 후 자동 롤백
@DataJpaTest
public class MemberRepositoryTest {
    @Autowired MemberRepository memberRepository;

    @Test
    public void testSave() {
        Member m = new Member("홍길동", 30);
        memberRepository.save(m);
    }
}

16. 성능 최적화 전략

  • 배치 처리 최적화: spring.jpa.properties.hibernate.jdbc.batch_size=100
  • 지연 로딩 최적화: EntityGraph, Fetch Join 활용
  • OSIV(Open Session In View): 성능 저하 원인 가능 → 비활성화 고려
spring.jpa.open-in-view=false

17. 실전 예제 모델링 (회원, 주문, 상품)

Member

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @OneToMany(mappedBy = "member")
    private List<Order> orders = new ArrayList<>();
}

Order

@Entity
public class Order {
    @Id @GeneratedValue
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Member member;
}

Spring은 백엔드 개발에서 가장 핵심적인 프레임워크 중 하나로, 어렵더라도 반드시 학습해 두어야 할 기술입니다

스프링과 JPA를 공부할 때 반드시 참고해야 할 점은  예제 없이 개념만 공부하지 말고 반드시 작은 실습 프로젝트를 병행해야 합니다!

그리고 한 번에 모든 기능을 마스터하려고 하기보다, 하나의 기능을 다양한 케이스로 반복하면서 자연스럽게 체득하는 방식이 가장 효과적입니다.

실습이 답이다.