enginner_s2eojeong

<실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발> 1편 본문

Backend/SpringBoot

<실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발> 1편

_danchu 2025. 2. 10. 19:26

<목차>

1. 들어가기 전 - 용어 정리

2. 도메인 분석 설계

3. 애플리케이션 구현 준비

 

작년 하반기에 스프링부트 스터디를 진행하며 노션에 정리해놓았던 내용들입니다. 
최근 연합 프로젝트를 진행하면서 당시 공부한 내용을 실제로 적용해볼 수 있었고 덕분에 JPA와 스프링 부트에 대한 개념을 더욱 확실히 다질 수 있었습니다. 이번에는 그 경험을 복습할 겸 좀 더 다듬고 정리된 형태로 블로그에도 공유해보려 합니다.

1. 들어가기 전 - 용어 정리

@Repository
public class MemberRepository {

    @PersistenceContext
    private EntityManager em;

    public Long save(Member member){
        em.persist(member);
        return member.getId();
    }

    public Member find(Long id){
        return em.find(Member.class, id);
    }
}

@Repository

  • 해당 클래스가 데이터 접근을 처리하는 repository 클래스 임을 나타냄.
  • repository는 데이터베이스와의 직접적인 상호작용을 캡슐화하여 애플리케이션의 다른 부분이 데이터베이스의 복잡한 동작을 신경 쓰지 않도록 함.
  • 예를 들어, CRUD (Create, Read, Update, Delete) 작업을 repository 계층에서 수행하고, 서비스 계층이나 비즈니스 로직에서는 이를 호출만 하면 됨.

@PersistenceContext

  • JPA에서 EntityManager를 주입하기 위한 annotation
  • Spring이나 JPA 환경에서는 EntityManager를 직접 생성하지 않고, @PersistenceContext 애너테이션을 통해 의존성 주입을 받음.

EntityManager

  • 데이터베이스와의 상호작용을 관리하는 JPA의 핵심 클래스
  • Entity 객체를 데이터베이스에 저장하고 조회, 수정, 삭제(CRUD) 등을 수행함.

save(Member member) 메서드

  • em.persist(member):
    • persist 메소드는 Member 객체를 데이터베이스에 저장함.
    • 해당 객체는 데이터베이스에서 관리되는 상태가 됨.

find(Long id) 메서드

  • em.find(Member.class, id)
    • Member.class 는 어떤 엔티티 타입을 조회할지 명시하는 역할 → 여기서는 Member 엔티티
    • id는 찾으려는 Member 객체의 고유 식별자(Primary Key)

2. 도메인 분석 설계

JPA에서의 연관관계 매핑 종류

  • 1:1 관계 (@OnetoOne)
    • 외래 키는 두 엔티티 중 한 곳에만 존재하며, 이 외래 키를 통해 서로 연결된다.
    • @JoinColumn을 사용하여 외래 키를 명시적으로 설정한다.
  • 1:N 관계 (@OnetoMany)
    • 외래 키는 N쪽 엔티티에 존재한다.
    • mappedBy 속성을 사용하여 연관 관계의 주인을 설정하고, 외래 키는 연관된 N쪽 엔티티에서 관리한다.
  • N:1 관계 (@ManytoOne)
    • 외래 키는 N쪽 엔티티에 존재하며, N쪽 테이블이 외래 키를 관리한다.
    • @JoinColumn을 사용하여 외래 키를 명시적으로 설정한다.
  • N:N 관계 (@ManytoMany) —> 실무에선 사용하지 않음
    • @JoinTable을 사용하여 연결 테이블을 정의한다.

주요 개념

  • @JoinColumn
    • 연관된 엔티티의 외래 키를 관리하는 칼럼을 명시적으로 설정하는 데 사용된다.
    • 외래 키가 있는 테이블에서(N쪽) 해당 키를 사용해 다른 엔티티와의 관계를 연결한다.
    • 주로 @ManyToOne 관계에서 많이 사용되며, 1:1 관계에서도 사용 가능하다.
  • @JoinTable
    • 다대다(N:N) 관계에서 중간 테이블을 생성할 때 사용된다.
    • 두 테이블 간의 다대다 관계를 관리하는 연결 테이블을 정의한다. 연결 테이블은 양쪽 엔티티의 외래 키를 가진다.
    • @ManyToMany 관계에서 사용된다.
  • mappedBy
    • 연관 관계의 주인이 아니고, 연관된 엔티티에서 외래 키를 관리하는 속성을 설정할 때 사용된다.
    • @OneToMany나 @ManyToMany에서 사용된다.

연관관계의 주인(Owner)과 비주인(Non-Owner)

  • 연관 관계의 주인:
    • 외래 키를 관리하는 엔티티
    • 주로 @ManyToOne이나 @JoinColumn이 있는 엔티티가 주인 역할이다.
  • 연관 관계의 비주인:
    • mappedBy로 설정된 엔티티
    • 외래 키를 직접 관리하지 않으며, 주인 엔티티에서 관리되는 외래 키를 참조한다.

‼️엔터티 설계시 주의점

1. 엔티티에는 가급적 Setter 사용X

  • 변경 포인트가 많아서 유지보수가 어렵다.

2. 모든 연관관계는 지연로딩으로 설정

  • 즉시로딩( EAGER )은 예측이 어렵고, 어떤 SQL이 실행될지 추적하기 어렵다. 특히 JPQL을 실행할 때 N+1 문제가 자주 발생한다. → 불필요한 쿼리를 줄여준다.
  • 실무에서 모든 연관관계는 지연로딩( LAZY )으로 설정해야 한다.
  • @XToOne(OneToOne, ManyToOne) 관계는 기본이 즉시로딩이므로 직접 지연로딩으로 설정해야 한다.

3. 컬렉션은 필드에서 초기화

  • 컬렉션 타입의 필드를 초기화하지 않으면, 해당 필드를 사용하는 시점에 그 필드가 null일 수도 있다.
List<Order> orders = null;
  • 이렇게 선언만 하고 초기화하지 않으면, 나중에 orders.add(order)와 같은 작업을 할 때 NPE가 발생한다. 왜냐하면 orders가 아직 생성되지 않았기 때문에 null 상태에서 메서드를 호출할 수 없기 때문이다.
private List<Order> orders = new ArrayList<>();
  • 이렇게 하면 필드가 항상 빈 리스트로 초기화되기 때문에, 나중에 그 필드를 사용할 때 null이 아닌 빈 리스트 상태에서 작업할 수 있다.

4. 양방향 연관 관계 매서드

  • 양방향 관계인 두 엔티티 간의 연관을 설정할 때, 서로의 필드를 동시에 업데이트하기 위한 역할
//==연관 관계 메서드==//
    public void setMember(Member member) {
        this.member = member; // Order의 member 필드 설정
        member.getOrders().add(this); // Member의 orders 리스트에 현재 Order 추가
    }
  • setMember 메서드는 Order 엔티티가 Member 엔티티와 연관 관계를 맺을 때 호출된다.
  • 이 메서드는 먼저 this.member = member로 현재 Order 객체가 가리키는 member를 설정한다.
  • member.getOrders().add(this)를 호출해서 Member 엔티티의 orders 리스트에 현재 Order 객체를 추가한다.
//==연관 관계 메서드==//
public void addOrderItem(OrderItem orderItem){
        orderItems.add(orderItem); // orderItems 리스트에 orderItem 추가
        orderItem.setOrder(this); // 현재 Order 객체를 orderItem에 설정
    }
  • OrderItem을 Order에 추가하는 메서드
  • orderItems.add(orderItem)으로 Order의 orderItems 리스트에 새 OrderItem을 추가한다.
  • orderItem.setOrder(this)를 호출해서 해당 OrderItem 객체가 가리키는 Order 필드를 현재 Order로 설정한다.

3. 애플리케이션 구현 준비

  • 요구사항 분석
  • 계층형 구조 사용
    • controller, web: 웹 계층
    • service: 비즈니스 로직, 트랜잭션 처리
    • repository: JPA를 직접 사용하는 계층, 엔티티 매니저 사용
    • domain: 엔티티가 모여 있는 계층, 모든 계층에서 사용

 

이번 글에서는 JPA를 활용한 웹 애플리케이션 개발의 기초적인 부분을 다뤘습니다.
이어지는 2편에서는 도메인 설계를 실제 코드로 구현하면서 회원, 상품, 주문 도메인의 개발 과정을 살펴보겠습니다.