모의 면접 준비

이유진·2024년 10월 9일

TIL

목록 보기
21/24
post-thumbnail

모의 면접 준비

1. JPA에서 Lazy Loading과 Eager Loading의 차이점과 각각의 장단점

  • JPA에서 엔티티의 연관 관계를 로딩하는 두 가지 방식

  • JPA

    • Java에서 ORM 기술 표준으로 사용되는 인터페이스
  • ORM

    • 객체와 관계형 데이터베이스의 데이터를 자동으로 매핑해주는 기술
    • SQL 쿼리를 직접 작성하지 않고도 데이터 베이스 작업을 수행할 수 있도록 해준다.
  • Hibernate

    • 자바 언어를 위한 ORM 프레임워크
    • JPA의 구현체로, JPA 인터페이스를 구현하고 내부적으로 JDBC API를 사용합니다.
  • Lazy Loading

    • 엔티티가 처음 로드될 때 연관된 엔티티를 즉시 로딩하지 않고, 해당 엔티티에 실제로 접근할 때 연관된 엔티티를 로딩하는 방식입니다.

    • 필요한 데이터만 로드해야 하는 상황이라면 Lazy Loading을 선택합니다.

    • 기본적으로 연관된 엔티티를 프록시 객체로 설정하고, 필요한 시점에 실제 데이터를 가져옵니다.

      • 프록시 객체란?

        • 실제 엔티티 대신에 대리 역할을 하는 객체

        • 즉, 데이터베이스에서 실제 데이터를 가져오기 전까지 엔티티의 "대리자"로서 존재하는 객체

        • Post 엔티티가 조회될 때, Comment는 로딩되지 않고 프록시 객체로 설정됩니다.

          이후, Post에서 Comment를 조회하는 메서드를 호출하면 프록시 객체가 실제 데이터베이스에서 Comment 데이터를 가져옵니다.

    • 장점

      • 초기 로딩 시 불필요한 데이터베이스 접근을 줄여 성능을 최적화할 수 있습니다.
      • 필요한 데이터만 로딩하여 메모리 사용량을 줄입니다.
    • 단점

      • 프록시 객체를 사용하므로, 실제 데이터베이스 접근이 필요한 시점까지 지연됩니다. 이로 인해, 데이터 접근 시점에 성능 저하가 발생할 수 있습니다.
    • 사용예시

      • 관계된 데이터가 항상 사용되지 않을 때, 예를 들어 게시글과 댓글 관계에서, 게시글만 볼 때는 댓글을 로딩할 필요가 없으므로 Lazy Loading이 유리합니다.
  • Eager Loading

    • 엔티티가 처음 로드될 때 연관된 모든 엔티티를 즉시 로딩하는 방식입니다.

    • 항상 함께 사용하는 데이터라면 Eager Loading을 선택해 초기 로딩 시 한 번에 데이터를 가져오는 것이 유리합니다.

    • 초기 쿼리에서 연관된 모든 엔티티를 함께 로딩하므로, 이후 데이터베이스 접근을 줄일 수 있습니다.

    • 장점

      • 연관된 데이터를 즉시 사용할 수 있으므로, 데이터 접근 시점에 추가적인 데이터베이스 접근이 필요 없습니다.
      • 복잡한 연관 관계를 한 번의 쿼리로 로딩할 수 있습니다.
    • 단점

      • 불필요한 데이터를 로드할 수 있어 초기 로딩 시 성능 저하가 발생할 수 있습니다.
      • 메모리 사용량이 증가할 수 있습니다.
    • 사용 예시

      • 관계된 데이터가 항상 함께 사용되는 경우, 예를 들어 주문과 주문 상세 정보처럼 주문을 조회할 때 상세 정보도 항상 필요한 경우 Eager Loading이 적합합니다.

2. JPA에서 N+1 문제를 해결하기 위한 방법

  • N+1 문제란?

    • 연관 관계가 설정된 엔티티 조회시, 관계 데이터 갯수 만큼 조회 쿼리가 추가로 발생하여 데이터를 읽어오는 현상
    • 원인 : 한쪽 테이블만 먼저 조회하고 연관 관계가 있는 다른 테이블은 따로 조회하기 때문
    • 관계가 많아지고 데이터가 커지면 N+1의 N이 데이터베이스 및 애플리케이션 전체 성능에 영향을 끼칠 수 있음
  • Fetch Join 사용

    • 쿼리에서 JOIN FETCH를 사용해서 연관된 엔티티를 함께 한번에 조회할 수 있습니다.
    • 연관된 데이터를 미리 로드하여 N번의 추가 쿼리를 방지할 수 있습니다.
    • 예를 들어, 회원과 주문 정보를 조회할 때 Fetch Join을 사용하면 한 번의 쿼리로 회원과 그에 해당하는 모든 주문을 가져올 수 있습니다.
  • MultipleBagFetchException

    • ToMany를 여러개 Fetch Join 할 경우 발생
    • To One은 몇개든 Fetch Join 가능
    • ToMany는 1개만 Fetch Join 가능
  • @BatchSize 어노테이션 사용

    • 연관된 엔티티를 한 번에 가져오는 양을 설정해주는 방법입니다. 이를 통해 N번의 쿼리를 줄이고, 일정한 크기만큼 묶어서 조회할 수 있게 해줍니다.
    • 예를 들어, @BatchSize(size = 10)으로 설정하면 최대 10개의 연관 엔티티를 한 번에 가져와 성능을 향상시킬 수 있습니다.(주로 @OneToMany에 붙여줌)
    • 다중 관계에서 fetch join 및 batch size 설정 대상 정하는 기준
      • 호출량 차이가 있다면? → 호출량이 잦은 대상에 fetch로 한번에 가져오기
      • 호출이 가변적이라면? → 호출이 적은 대상에 batch size로 필요할때 적절한 크기 만큼 가져오기
      • 데이터양 차이가 난다면? → 데이터가 적은 대상은 fetch로 한번에 가져오고, 많은 대상은 batch size로 적절한 크기 만큼 가져오기

3. 단위 테스트와 통합 테스트의 차이점과 각각의 장단점

  • 단위테스트

    • 코드의 작은 단위(주로 메서드나 클래스)를 개별적으로 테스트하여 원하는 동작을 하는지 확인하는 것

    • 장점

      • 빠른 실행: 테스트 범위가 작고 독립적이기 때문에 실행 속도가 빠릅니다.
      • 버그 발견 용이: 코드의 작은 부분에서 발생하는 버그를 빨리 찾을 수 있습니다.
      • 코드 작성과 동시에 테스트를 만들어 기능을 검증하기 좋습니다.
    • 단점

      • 외부 시스템 검증 불가: 데이터베이스, 네트워크, 파일 시스템 등 외부 시스템과의 통합 검증은 불가능합니다.
      • 개별 모듈은 정상적으로 동작하더라도, 모듈 간 연동 문제가 있는 경우 단위 테스트로는 발견할 수 없습니다.

  • 통합테스트

    • 애플리케이션의 여러 모듈이 실제로 통합된 상태에서 상호작용이 올바르게 이루어지는지를 테스트하는 것입니다.

    • @SpringBootTest : 전체 애플리케이션을 띄워서 실행

    • 장점

      • 전체 시스템 동작 검증: 실제 환경과 유사한 상태에서 테스트하므로 모듈 간의 통합 문제를 찾기 좋습니다.
      • 실제 의존성 검증 가능: 데이터베이스, API 등 실제 환경과 상호작용하며 테스트할 수 있어 예상치 못한 문제를 발견하기 쉽습니다.
    • 단점

      • 느린 실행 속도: 외부 시스템과의 통신이나 환경 설정 등이 포함되므로 실행 시간이 길어질 수 있습니다.
      • 설정이 복잡: 테스트 환경을 실제 환경처럼 구성해야 하므로 설정이 복잡할 수 있습니다.
      • 디버깅 어려움: 여러 모듈이 함께 동작하기 때문에 문제 발생 시 원인을 찾기 어려울 수 있습니다.

  • Testcode 어떻게 작성하는지?

    • 서비스단 로직은 주로 단위테스트로

    • 컨트롤러나 레포지토리는 통합테스트로

      • 컨트롤러도 단위테스트 가능(요청 처리, 파라미터 검증, 응답 형식 등을 검증 )
      • @WebMvcTest , mockMvc 주입받아서 테스트
    • 테스트 코드 작성 도구와 프레임워크

      • JUnit : @Before, @After등 / assertEquals
      • Mockito : 모킹 객체를 생성하기 위한 라이브러리, 외부 의존성을 모킹하여 테스트 작성 가능합니다.

  • Testcode 작성하는게 왜 중요한가

    • 코드의 품질과 유지보수성을 높이는 데 기여
    • 테스트 코드로 비지니스 로직을 검증함으로써 버그를 빠르게 찾을 수 있고 이로인해 대처도 빠르게 가능합니다.
    • 코드 변경 시 기존 기능이 올바르게 동작하는지 검증 등을 통해 발생할 수 있는 문제를 미리 방지할 수 있습니다.
    • 처음에는 테스트 코드를 작성하는 데 시간이 들 수 있지만, 장기적으로는 디버깅과 문제 해결 시간을 줄여줍니다.
    • 테스트는 어떻게 라는 내부 구현이 아닌 무엇을 이라는 결과 검증에 집중해야합니다.

4. QueryDSL을 사용하여 복잡한 동적 쿼리를 작성하는 방법

  • QueryDSL이란?

    • 자바에서 타입 안전하고 가독성 높은 동적 쿼리를 작성하기 위한 프레임워크입니다.
  • 특징

    • 타입 안전성 제공
      • Q클래스 덕분에 컴파일 시점에서 쿼리에 사용된 엔티티와 필드 이름을 검증할 수 있어, 잘못된 쿼리로 인한 오류를 방지할 수 있습니다.
      • QClass? : dependency를 넣어주고 빌드 시 자동으로 생성됩니다.
      • Q클래스는 해당 엔티티의 속성을 필드로 가지므로, 안전하게 참조할 수 있다.
    • 조건에 따라 동적으로 쿼리를 생성할 수 있어, 복잡한 조건을 처리하는 데 유용하다.
    • 페이징, 정렬 등 복잡한 쿼리 작성을 쉽게 할 수 있습니다.

  • QueryDSL 프로젝트에서 실제로 써본적 있는지

    • 프로젝트에서 게시물과 연관된 댓글 테이블을 조인하여 페이징된 게시물 목록을 가져오는 쿼리를 작성했고, 쿼리 결과는 게시물의 제목, 내용뿐만 아니라 연관된 댓글 정보를 포함하도록 했습니다. 페이징과 정렬을 적용하여 동적으로 조건을 구성하여 쿼리를 작성해보았습니다.

  • QueryDSL 작성방법

    • JPA 엔티티를 기반으로 생성된 Q타입 클래스를 사용해, 타입 안전한 쿼리를 작성합니다.
    • QueryDSL 설정을 위한 config class를 생성한 후, EntityManager를 주입받아 JPA와 상호작용할 수 있도록 하고, JPAQueryFactory를 빈으로 등록하여 QueryDSL을 사용할 수 있도록 설정합니다.
    • QueryRepository 인터페이스를 만들어 동적 쿼리 메서드를 정의하고, 이를 구현하는 QueryRepositoryImpl 클래스를 작성해 실제 쿼리 로직을 구현합니다.
    • Repository에서는 JPARepository와 interface repository 다중상속을 받아 기본적은 JPA 기능과 복잡한 QueryDSL 기반 쿼리 모두 사용할 수 있습니다.
  • QueryDSL을 이렇게 분리하여 작성하는 이유

    • 기본 CRUD기능과 QueryDSL 관련 로직을 독립적으로 관리할 수 있어서 코드 변경이나 확장이 쉬워집니다.

5. CS 문제 : 트랜젝션

  • 트랜잭션이란?

    • 여러개의 작업을 하나로 묶은 실행 유닛
    • DB의 상태를 변환시키는 기능을 수행하기 위한 하나 이상의 쿼리를 모아 놓은 하나의 작업 단위
  • 특성(ACID)

    • 원자성(Atomicity) : 모든 작업이 완전히 수행되거나, 전혀 수행되지 않아야 한다는 속성입니다. 중간에 오류가 발생하면, 트랜잭션 내의 모든 작업이 롤백되어 처음 상태로 되돌아갑니다.
    • 일관성(consistency) : 트랜잭션이 완료된 후에도 데이터베이스의 상태가 일관된 상태를 유지해야 한다는 속성입니다.
    • 격리성, 고립성(Isolation) : 여러 트랜잭션이 동시에 수행될 때, 각 트랜잭션은 서로의 작업에 영향을 주지 않아야 합니다.
    • 지속성(Durability) : 트랜잭션이 성공적으로 완료된 후에는 그 결과가 영구적으로 데이터베이스에 반영되어야 한다는 속성입니다. 시스템 오류가 발생하더라도, 트랜잭션의 결과는 손실되지 않습니다.

  • 트랜잭션 필요성

    • A은행에서 B은행으로 송금한다고 가정합니다.
    • 송금하던 중 알 수 없는 오류가 발생하여 A은행계좌에서 돈이 빠져 나갔는데 B은행 계좌에는 입금이 되지않는 상황이 발생하게 됩니다.
    • 이런 경우에, 거래가 성공적으로 모두 끝난 경우에만 완전한 거래로 승한하고, 거래 도중 뭔가 오류가 발생했을때는 이 거래를 아예 처음부터 없었던 것처럼 되돌립니다.
    • 이렇게 거래의 안정성을 확보하는 방법이 트랜잭션입니다.
    • 트랜잭션은 데이터베이스의 신뢰성과 작업 도중 오류가 발생해도 데이터베이스가 일관된 상태를 유지할 수 있는 핵심 개념으로, 이를 통해 복잡한 비즈니스 로직을 안정적으로 처리할 수 있습니다.
  • 트랜잭션 어떤경우에 쓰는지

    • 주문 처리 시스템: 여러 상품을 주문할 때, 재고를 차감하고 결제 정보를 업데이트하는 여러 작업이 하나의 트랜잭션으로 처리됩니다. 하나라도 실패하면 주문 전체가 무효화됩니다.

  • 트랜잭션 격리 수준(Isolation Level)

    트랜잭션 격리 수준은 동시에 실행되는 트랜잭션 간의 데이터 일관성을 어떻게 유지할지를 결정합니다 격리 수준에 따라 읽기 작업의 일관성이나 동시성 제어가 달라집니다:

    1. READ UNCOMMITTED: 다른 트랜잭션에서 커밋되지 않은 데이터까지 읽을 수 있습니다(Dirty Read 허용).

    2. READ COMMITTED: 다른 트랜잭션에서 커밋한 데이터만 읽을 수 있습니다(Dirty Read 방지).

    3. REPEATABLE READ: 트랜잭션 동안 읽은 데이터는 변경되지 않도록 보장합니다(Phantom Read 방지).

    4. SERIALIZABLE: 가장 높은 격리 수준으로, 모든 트랜잭션이 순차적으로 실행되도록 합니다.


  • @Transactional 동작 원리

    • @ Transactional이란?

      • 스프링에서 많이 사용되는 선언적 트랜잭션 방식으로, Spring AOP를 이용
    • Aop

      • Spring AOP는 기본적으로 프록시 방식으로 동작한다. 프록시 패턴이란 어떤 객체를 사용하고자 할 때, 객체를 직접적으로 참조하는 것이 아니라, 해당 객체를 대행(대리, proxy)하는 객체를 통해 대상 객체에 접근하는 방식을 말한다.
      • Aspect : Advice + PointCut로 AOP의 기본 모듈
      • Advice : Target에 제공할 부가 기능을 담고 있는 모듈
      • Target : Advice가 부가 기능을 제공할 대상(Advice가 적용될 비즈니스 로직)
      • JoinPoint : Advice가 적용될 위치
        • 메서드 진입 지점, 생성자 호출 시점, 필드에서 값을 꺼내올 때 등 다양한 시점에 적용 가능
        • PointCut : Target을 지정하는 정규 표현식
    • Aop가 프록시 방식을 사용하는 이유

      • 1. 런타임 시점의 삽입 가능:
        Spring AOP는 컴파일 시점이 아니라 런타임 시점에 프록시 객체를 생성하여 부가적인 기능을 주업무 코드에 삽입한다. 이는 AOP를 적용하는 대상 클래스의 바이트코드를 수정하지 않고, 부가적인 기능을 주입할 수 있게 한다. 따라서 애플리케이션을 실행 중에 동적으로 관심사를 적용할 수 있다.

      • 2. 코드 재사용과 모듈화:
        여러 클래스나 메서드에서 공통적으로 필요한 기능(예: 로깅, 보안 체크, 트랜잭션 관리 등)을 하나의 공통 모듈로 분리하여 관리할 수 있다. 이를 AOP로 구현한 프록시 객체를 여러 클래스에서 재사용할 수 있으며, 이로 인해 코드 중복을 줄이고, 유지보수성을 향상시킨다.

      • 3. 관심사의 분리:
        AOP는 관심사를 주업무 코드와 분리하여 코드의 가독성을 높이고 유지보수를 용이하게 한다. 주업무 코드는 핵심 비즈니스 로직에만 집중할 수 있고, 부가적인 기능은 프록시 객체가 담당하게 된다. 이로 인해 코드의 응집도가 증가하고 결합도가 감소하여 코드의 이해와 관리가 쉬워진다.


  • 트랜잭션 전파범위와 생명주기

    • 트랜잭션의 시작과 종료, 전파 방식에 따라 트랜잭션이 어떻게 관리되는지를 결정하는 개념입니다.

    • 이를 통해 트랜잭션의 범위와 실행 흐름을 제어할 수 있습니다.

    • 트랜잭션 전파범위

    • 전파 범위는 트랜잭션이 이미 진행 중일 때, 새로 호출된 메서드가 기존 트랜잭션에 참여할지 아니면 새로 트랜잭션을 시작할지를 결정합니다.

      • REQUIRED (기본값):

        • 현재 트랜잭션이 있으면 기존 트랜잭션에 참여하고, 없으면 새로운 트랜잭션을 시작합니다.

        REQUIRES_NEW:

        • 항상 새로운 트랜잭션을 시작합니다. 기존 트랜잭션이 있으면 일시 중단됩니다.

        SUPPORTS:

        • 현재 트랜잭션이 있으면 참여하고, 없으면 트랜잭션 없이 실행합니다.

        MANDATORY:

        • 현재 트랜잭션이 반드시 있어야 하며, 없으면 예외를 발생시킵니다.

        NOT_SUPPORTED:

        • 현재 트랜잭션이 있으면 일시 중단하고 트랜잭션 없이 실행합니다.

        NEVER:

        • 트랜잭션 없이 실행해야 하며, 현재 트랜잭션이 있으면 예외를 발생시킵니다.

        NESTED:

        • 기존 트랜잭션 내에서 중첩 트랜잭션을 시작합니다. 트랜잭션을 롤백할 때 현재 트랜잭션만 롤백됩니다.
    • 트랜잭션 생명주기

    • 트랜잭션이 시작되고 종료될 때까지의 과정을 말합니다.

      • 트랜잭션 시작(Begin)

        • 트랜잭션이 시작되며, 데이터베이스와의 연결이 설정됩니다. @Transactional이 선언된 메서드가 호출될 때 트랜잭션 매니저가 트랜잭션을 시작합니다.

        트랜잭션 진행(Execution)

        • 트랜잭션 내에서 데이터베이스에 대한 모든 작업이 수행됩니다. 이 단계에서는 작업이 커밋되기 전까지 데이터베이스 변경사항이 일시적으로 유지됩니다.

        트랜잭션 커밋(Commit)

        • 트랜잭션 내의 모든 작업이 성공적으로 완료되면, 변경 사항이 영구적으로 데이터베이스에 반영됩니다. 커밋이 완료된 후에야 데이터가 최종적으로 저장됩니다.

        트랜잭션 롤백(Rollback)

        • 트랜잭션 내의 작업 중 예외가 발생하거나 강제로 롤백이 호출되면, 모든 변경 사항이 취소되고 원래 상태로 복원됩니다. 트랜잭션 매니저는 롤백 시점에 모든 데이터를 원래대로 되돌립니다.

  • 관련 꼬리질문

    • 예외 발생 시 트랜잭션 롤백을 어떻게 제어할 수 있나요?

      • "@Transactional은 기본적으로 RuntimeException이나 Error가 발생할 때 트랜잭션을 롤백합니다. rollbackFor 속성을 사용하여 특정 예외에 대해 롤백을 설정할 수 있고, noRollbackFor 속성으로 특정 예외가 발생해도 롤백하지 않도록 제어할 수 있습니다. 이를 통해 예외 상황에 따른 트랜잭션 제어가 가능합니다."
    • @Transactional을 클래스와 메서드에 동시에 적용하면 어떻게 동작하나요?

      • "클래스와 메서드 레벨에 @Transactional을 동시에 적용하면, 메서드 레벨의 설정이 우선적으로 적용됩니다. 클래스 레벨에 선언된 @Transactional은 기본 설정으로 사용되고, 메서드에 별도로 지정된 옵션이 있으면 메서드의 옵션이 클래스의 설정을 덮어씁니다."
    • 트랜잭션 매니저는 어떤 역할을 하나요?

      • "트랜잭션 매니저는 트랜잭션의 시작, 커밋, 롤백 등을 관리하는 역할을 합니다. Spring에서는 여러 트랜잭션 매니저를 지원하며, JpaTransactionManager는 JPA와 함께 사용하고, DataSourceTransactionManager는 JDBC 기반의 트랜잭션을 관리합니다. 적절한 트랜잭션 매니저를 선택하여 데이터베이스와의 트랜잭션을 일관되게 관리할 수 있습니다."
profile
🙌중요한건 꺾였는데도 그냥 하는 마음

0개의 댓글