[24.07.01] 내일배움캠프 일차 JAVA TIL - 모의 면접 준비

2024. 7. 1. 13:39T.I.L

오늘 한 일

  • JPA 심화 완강
  • AWS 강의 수강

 


질문

  1. JPA에서 Lazy Loading과 Eager Loading의 차이점은 무엇인가요? 각각의 장단점에 대해 설명해 주세요.
  2. JPA에서 N+1 문제를 해결하기 위한 방법을 설명해 주세요.
  3. 통합 테스트와 단위 테스트의 차이점에 대해서 설명해주세요.
  4. 통합 테스트과 단위 테스트의 장/단점에 대해서 설명해주세요.
  5. 레이어별로 나누어서 Slice Test 를 하는 이유에 대해서 설명해주세요.
  6. 테스트 코드를 직접 짰을 때, 느낀 테스트 코드 작성의 필요성을 설명해주세요.
  7. JPA와 Hibernate의 차이점은 무엇인가요?
  8. QueryDSL을 사용하여 복잡한 동적 쿼리를 작성하는 방법을 설명해 주세요.
  9. 프로젝트에서 좋아요 기능을 구현할 때, 특정 사용자가 특정 게시글을 이미 좋아요 했는지 확인하는 방법을 설명해 주세요.
  • JPA에서 Lazy Loading과 Eager Loading의 차이점은 무엇인가요? 각각의 장단점에 대해 설명해 주세요.
    • 차이점: 엔티티가 로딩될 때 즉시 관련된 모든 엔티티를 함께 로딩합니다. → 한번의 쿼리로 연관된 데이터를 모두 가져옴 (이미 필요한 데이터가 로딩되어 있음)
    • 장점:
      • 데이터베이스에서 한 번의 쿼리로 필요한 데이터를 모두 가져오므로 접근 속도가 빠릅니다.
      • 트랜잭션 내에서 필요한 모든 데이터가 이미 로딩되어 있어 안전합니다.
    • 단점:
      • 필요하지 않은 데이터까지 모두 로딩하므로 메모리 사용량이 많아질 수 있습니다.
      • 초기 로딩 시간이 길어질 수 있습니다.
    LAZY 로딩(지연 로딩) ToMany에서 기본값
    • 차이점: 관련된 엔티티가 실제로 사용될 때까지 로딩을 지연시킵니다.
    • 참조 객체들의 데이터들은 무시하고 해당 엔티티의 데이터만을 가져오는 방식
    • 장점:
      • 초기 로딩 시 필요한 데이터만 가져오기 때문에 메모리 사용량이 적습니다.
      • 초기 로딩 속도가 빠릅니다.
    • 단점:
      • 관련된 엔티티를 실제로 사용할 때마다 추가 쿼리가 발생할 수 있습니다.
      • 트랜잭션 외부에서 접근 시 LazyInitializationException이 발생할 수 있습니다.
    트랜잭션 관점에서의 주의사항:
    • LAZY 로딩은 엔티티가 영속 상태일 때만 가능합니다. 준영속 또는 비영속 상태에서는 LAZY 로딩이 불가능하며 LazyInitializationException이 발생합니다.
    • 따라서 트랜잭션이 종료된 후 LAZY 로딩을 시도하면 예외가 발생하므로, 필요한 데이터는 트랜잭션 내에서 미리 로딩하거나, DTO를 사용하여 필요한 데이터를 트랜잭션 내에서 변환하여 사용해야 합니다.
    • @ManyToOne과 @OneToOne 관계의 기본 로딩 방식은 EAGER입니다.
    • @OneToMany와 @ManyToMany 관계의 기본 로딩 방식은 LAZY입니다.
  • EAGER 로딩(즉시 로딩) ~ToOne에서 기본값
  • JPA에서 N+1 문제를 해결하기 위한 방법을 설명해 주세요.
    • 정의: 하나의 쿼리로 N개의 엔티티를 조회한 후, 각 엔티티와 연관된 데이터를 조회하기 위해 추가로 N번의 쿼리가 실행되는 문제를 말합니다. 결과적으로 총 N+1번의 쿼리가 실행되어 성능에 큰 영향을 미칩니다.
    • → 데이터 개수만큼 엔티티 연관관계 조회 쿼리가 발생하는 현상
    해결방법:
    1. Fetch Join:
      • 하나의 쿼리로 연관된 엔티티를 함께 조회
      • 예: SELECT p FROM Parent p JOIN FETCH p.children
      • 장점: 쿼리 수가 줄어들어 성능이 개선됩니다.
      • 단점: 너무 많은 데이터를 한 번에 로딩할 경우 메모리 사용량이 많아질 수 있습니다.
    2. Batch Fetching: (배치 사이즈 조절)
      • 한번에 여러 연관된 엔티티를 배치로 가져오는 방식입니다.
      • JPA 설정에서 @BatchSize를 사용하여 일괄 로딩 크기를 지정할 수 있습니다.
      • 예: @BatchSize(size = 10)
    3. Entity Graph:
      • 엔티티 그래프를 정의하여 특정 조회 시점에 필요한 연관 엔티티를 명시적으로 지정합니다.
      • 예: @EntityGraph(attributePaths = {"children"})
      • 장점: 동적으로 필요한 데이터를 선택적으로 로딩할 수 있습니다.
    실제로는 한가지 방법을 택하기 보다는 지연로딩(LAZY) 사용하고 성능 최적화에서 Fetch조인을 사용하고 Batch size를 1000이하의 값으로 연관관계는 꼭 필요할때만 사용하는게 좋다.
    1. 통합 테스트와 단위 테스트의 차이점
  • 단위 테스트:
    • 정의: 애플리케이션의 가장 작은 단위(보통 메서드나 클래스)를 독립적으로 테스트하는 것.
    • 목적: 특정 기능이 올바르게 작동하는지 확인.
    • 특징:
      • 다른 컴포넌트와 독립적으로 테스트.
      • Mock 객체를 많이 사용.
      • 빠른 실행 속도.
      • 개발 초기에 작성 (TDD : 테스트 코드를 먼저 작성하는 개발 방법론)

      해당 부분만 독립적으로 테스트하기 때문에 어떤 코드를 리팩토링하여도 빠르게 문제 여부를 확인할 수 있다.
      테스팅에 대한 시간과 비용을 절감할 수 있다.
      새로운 기능 추가 시에 수시로 빠르게 테스트 할 수 있다.
      리팩토링 시에 안정성을 확보할 수 있다.
      코드에 대한 문서가 될 수 있다.
      
      또한 좋고 깨끗한 테스트 코드는 FIRST라는 5가지 규칙을 따라야 한다.
      Fast: 테스트는 빠르게 동작하여 자주 돌릴 수 있어야 한다.
      Independent: 각각의 테스트는 독립적이며 서로 의존해서는 안된다.
      Repeatable: 어느 환경에서도 반복 가능해야 한다.
      Self-Validating: 테스트는 성공 또는 실패로 bool 값으로 결과를 내어 자체적으로 검증되어야 한다.
      Timely: 테스트는 적시에 즉, 테스트하려는 실제 코드를 구현하기 직전에 구현해야 한다.
      
    통합 테스트:
    • 정의: 여러 단위가 결합된 기능이나 모듈이 올바르게 상호작용하는지 테스트하는 것.
      • 최소 두개 이상의 단위가 잘 작동하는 지, 개발 후반부에 시행합니
    • 목적: 시스템의 각 부분이 함께 제대로 작동하는지 확인.
    • 특징:
      • 실제 환경과 비슷한 조건에서 테스트.
      • 데이터베이스, 네트워크 등 외부 시스템과의 통합 테스트 포함.
      • 상대적으로 느린 실행 속도.
    1. 단위 테스트와 통합 테스트의 장/단점
  • 단위 테스트:
    • 장점:
      • 빠른 피드백: 코드 변경 후 즉각적인 피드백 가능.
      • 디버깅 용이: 특정 기능만 테스트하므로 문제를 쉽게 찾아낼 수 있음.
      • 코드 리팩토링: 코드 변경 시 안정성을 보장.
    • 단점:
      • 전체 시스템의 동작 보장 어려움: 개별 단위가 올바르게 작동해도 통합 시 문제가 발생할 수 있음.
      • Mocking의 필요성: 실제 환경과 다를 수 있음.
    통합 테스트:
    • 장점:
      • 시스템 전반의 동작 확인: 실제 사용 환경과 유사한 조건에서 테스트.
      • 외부 시스템과의 통합 확인: 데이터베이스, 네트워크 등과의 상호작용 테스트 가능.
    • 단점:
      • 느린 실행 속도: 실제 시스템과의 통합 때문에 시간 소요.
      • 디버깅 어려움: 문제 발생 시 원인 파악이 어려울 수 있음.
    1. 레이어별로 나누어서 Slice Test를 하는 이유
  • Slice Test:
    • 특정 레이어(Controller, Service, Repository 등)에 대한 테스트를 수행하여 각 레이어의 동작을 검증하는 테스트 방법.
      • @WebMvcTest
      • @WebFluxTest
      • @DataJpaTest
      • @JsonTest
      • @RestClientTest
    각 레이어의 독립적인 테스트를 진행하면 기능 구현과 동시에 테스트를 하기에 간 편해집니다. 스프링 설정을 따로 해 줄 필요도 없을뿐더러, DB 연결도 필요 없고, 테스트할 때마다 빈을 로드하지 않아도 되어 빠른 속도로 테스트를 할 수 있게 됐습니다. 또한, 테스트 할 때 불필요한 정보들이 줄어들면서 테스트 코드의 가독성이 올라가게 됐습니다.
    1. 신속한 피드백: 단위 테스트처럼 빠르게 실행되어 신속한 피드백을 제공.
    2. 격리된 환경: 특정 레이어만 테스트하므로 다른 레이어의 영향을 받지 않고 독립적으로 테스트 가능.
    3. 문제 파악 용이: 각 레이어별로 테스트하므로 문제가 발생한 레이어를 쉽게 파악할 수 있음.
    4. 효율적인 리소스 사용: 통합 테스트에 비해 필요한 리소스가 적고, 설정이 간단함.
  • 이유:
  • 테스트 코드 작성의 필요성
    1. 코드 안정성 확보: 변경 시 기존 기능이 올바르게 작동하는지 확인할 수 있어 안정성을 높임.
    2. 디버깅 용이성: 버그 발생 시 빠르게 원인을 파악하고 수정 가능.
    3. 자동화 가능: 지속적인 통합(CI) 및 배포(CD) 과정에서 자동화된 테스트 실행으로 품질 보장.
    4. 문서화 역할: 테스트 코드는 코드의 사용 방법과 예상 결과를 보여주는 문서화 역할을 함.
    5. 리팩토링 지원: 기존 코드의 동작을 보장하면서 리팩토링할 수 있어 유지보수에 용이.
  • 테스트 코드를 작성하면서 느낀 필요성:
  • JPA와 Hibernate의 차이점
    • 정의: 자바에서 ORM(Object-Relational Mapping)을 위한 표준 인터페이스.
    • 역할: 애플리케이션과 데이터베이스 사이의 데이터 매핑을 위한 표준을 제공.
    • 구현체: Hibernate, EclipseLink, OpenJPA 등이 JPA의 구현체.
    ORM즉, 객체가 테이블이 되도록 매핑 시켜주는 것을 말합니다.생산성이 매우 높아집니다.그래서 나중에 다루게 될 JPQLQueryDSL 등을 사용하거나 한 프로젝트 내에서 Mybatis와 JPA를 같이 사용하기도 합니다.
    • 영속성 컨텍스트(persistence context) : 엔티티를 영구 저장하는 환경 데이터베이스와 영속성 컨텍스트를 통해 연결되있는 엔티티(객체)
    Hibernate:
    • 정의: JPA를 구현한 ORM 프레임워크 중 하나.
    • 역할: JPA의 표준을 따르며, 추가적인 기능(캐싱, 데이터베이스 벤더별 기능 등)을 제공.
    • 특징: 다양한 기능과 유연성을 제공하여 JPA를 사용하는 개발자들에게 자주 선택됨.
    • spring starter - data jpa 의존성에 이미 구현체가 포함되어있어 jpa와 호환이 잘된
    객체에 비지니스 책임을 위임할 수 있어요.
    객체를 불러올 때, 연관된 객체 또한 함께 불러오기 때문에, 
    SQL에 의존적이지 않은 개발을 할 수 있어요.
    즉, SQL 중심이 아닌 객체 중심의 개발이 가능해요
  • 그러나 query가 복잡해지면 ORM으로 표현하는데 한계가 있고, 성능이 raw query에 비해 느리다는 단점이 있는데요,
  • ORM을 이용하면 SQL Query가 아닌 직관적인 코드(메서드)로서 데이터를 조작할 수 있습니다.
  • ORM이란 객체와 DB의 테이블이 매핑을 이루는 것을 말합니다. (Java 진영에 국한된 기술은 아닙니다.) - 애플리케이션과 데이터베이스 사이의 중간계층 - 트랜잭션이 완료되면 데베에 반
  • JPA (Java Persistence API):
  • QueryDSL을 사용하여 복잡한 동적 쿼리를 작성하는 방법
    1. 기본 설정:
      • build.gradle에 QueryDSL 관련 의존성 추가:
      • groovy코드 복사 dependencies { implementation 'com.querydsl:querydsl-jpa' annotationProcessor 'com.querydsl:querydsl-apt' }
    2. Q 클래스 생성:
      • 엔티티 클래스를 기반으로 Q 클래스를 생성.
      • 예: QMember qMember = QMember.member;
    3. 쿼리 작성:
      • JPAQueryFactory를 사용하여 동적 쿼리를 작성:
      • java코드 복사 JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager); List<Member> members = queryFactory.selectFrom(QMember.member) .where(QMember.member.age.gt(30)) .fetch();
    4. 동적 조건 추가:
      • BooleanBuilder를 사용하여 조건을 동적으로 추가:
      • java코드 복사 BooleanBuilder builder = new BooleanBuilder(); if (age != null) { builder.and(QMember.member.age.gt(age)); } if (name != null) { builder.and(QMember.member.name.eq(name)); } List<Member> members = queryFactory.selectFrom(QMember.member) .where(builder) .fetch();
  • 프로젝트에서 좋아요 기능을 구현할 때, 특정 사용자가 특정 게시글을 이미 좋아요 했는지 확인하는 방법
    1. 엔티티 설정:
      • User와 Post 엔티티 사이에 Like 엔티티를 만들어 다대다 관계를 매핑.
      • 예:
      • java코드 복사 @Entity public class Like { @Id @GeneratedValue private Long id; @ManyToOne private User user; @ManyToOne private Post post; }
    2. 레포지토리 메서드 작성:
      • 특정 사용자가 특정 게시글을 좋아요 했는지 확인하는 쿼리 작성:
      • java코드 복사 public interface LikeRepository extends JpaRepository<Like, Long> { boolean existsByUserAndPost(User user, Post post); }
    3. 서비스에서 사용:
      • 서비스 클래스에서 레포지토리 메서드를 사용하여 좋아요 상태 확인:
      • java코드 복사 @Service public class LikeService { @Autowired private LikeRepository likeRepository; public boolean isLikedByUser(User user, Post post) { return likeRepository.existsByUserAndPost(user, post); } }
    이렇게 하면 특정 사용자가 특정 게시글을 이미 좋아요 했는지 쉽게 확인할 수 있습니다.
  • 트랜잭션이 뭐고 왜 필요한지, 예시를 들어서 설명해주세요
    • 정의: 데이터베이스의 상태를 변화시키는 작업의 논리적 단위
    • 특징 (ACID): 원자/일관/독립/영속
      • Atomicity: 모든 작업이 완전히 성공하거나 완전히 실패해야 합니다.
      • Consistency: 트랜잭션이 완료되면 데이터베이스가 일관성 있는 상태로 유지됩니다.
      • Isolation: 트랜잭션은 다른 트랜잭션으로부터 독립적으로 실행되어야 합니다.
      • Durability: 트랜잭션이 성공하면 그 결과는 영구적으로 반영됩니다.
    필요성:
    • 데이터 일관성 유지
    • 오류 발생 시 데이터 복구 가능
    • 동시에 여러 사용자 접근 시 데이터 무결성 보장
    예시:
    • 은행 계좌 이체: A 계좌에서 B 계좌로 돈을 이체하는 경우, A 계좌에서 출금과 B 계좌로 입금이 모두 성공해야 합니다. 둘 중 하나라도 실패하면 전체 작업이 취소되어야 합니다.
    코드에서 사용:
    1. 트랜잭션 어노테이션 사용:
    2. java코드 복사 @Transactional public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) { // 출금 로직 // 입금 로직 }
    3. 어노테이션 동작 원리:
      • @Transactional 어노테이션이 붙은 메서드는 프록시 객체에 의해 트랜잭션이 시작되고 종료됩니다.
      • 트랜잭션이 시작될 때 데이터베이스 커넥션이 생성되고, 메서드 실행이 완료되면 커밋 또는 롤백됩니다.
      • 예외가 발생하면 트랜잭션이 롤백되고, 성공적으로 완료되면 커밋됩니다

<엔티티의 생명주기 4가지>

: 엔티티를 영구 저장하는 환경, 논리적인 개념

: 애플리케이션 - DB 사이에서 객체를 보관하는 가상의 DB 역할 (→ 플러시 시점에 DB에 반영)

  1. 비영속 상태 (new/transient)

객체만 생성하고 엔티티와 연결X

  1. 영속 상태 (managed) → @Id로 매핑된 키값이 있어야함

객체를 생성하고 저장한 상태,

영속성 컨텍스트가 관리하는 엔티티(1차캐시/쓰기지연저장소에 있는 상태)

ex) em.persist(member);

//객체를 생성만 한 상태(비영속)
Member member = new Member();
member.setId("member1");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

//객체를 저장한 상태(영속)
em.persist(member);
  1. 준영속 상태 (detached)

엔티티가 영속성 컨텍스트에서 분리 ex) em.detached( );

  1. 삭제 상태 (removed)

엔티티 삭제 ex) em.remove( );