seungriyou / spring-study

자바 스프링 부트를 배워봅시다 🔥
0 stars 0 forks source link

[스프링 부트] 궁금증 해결 노트 #11

Open seungriyou opened 9 months ago

seungriyou commented 9 months ago

강의를 들으며 궁금했던 점들을 질문 게시판을 통해 해결한 기록

Contents

seungriyou commented 9 months ago

엔티티를 생성하는 세 가지 방법

  1. 간단하면 그냥 생성자를 사용
  2. 조금 의미를 부여해야 하거나 비즈니스 로직이 필요하다면 정적(static) 팩토리 메서드 사용
  3. 파라미터가 너무 많거나 복잡하면 빌더 패턴 사용, 실용적인 관점에서 롬복 빌더를 생성자와 함께 사용(생성자 위에 애노테이션 붙여서)

    setter를 열고 싶지 않을 때 빌더 패턴을 사용한다!


[!caution] 중요한 것은 바로 생성 시점에 필요한 값을 다 넣고, 꼭 필요한 경우에만 수정할 수 있게 엔티티를 설계하는게 유지보수에 좋다는 것이다!


Reference

seungriyou commented 9 months ago

@Transactional(readOnly = true)

@Transactional(readOnly = true)의 기능은 다음과 같다.

  1. 데이터베이스 커넥션에 읽기 전용 정보를 넘겨준다.
  2. 트랜잭션 커밋 시점에 플러시를 호출하지 않는다.

    dirty checking 같은 부분이 생략되므로 성능 최적화가 발생한다. 또한, 엔티티를 변경해도 변경 내용이 반영되지 않는다.


Reference

seungriyou commented 9 months ago

즉시 로딩 vs. 페치 조인

JPA에서 모든 연관관계는 꼭! LAZY로 설정해야 한다. 실무에서 EAGER는 그냥 없다고 생각하는 것이 더 좋다.

그렇게 해야 member만 필요해서 조회할 때, member만 조회가 됩니다.

그런데 때때로 특정 기능에서는 memberteam함께 필요한 경우가 있다. 이때 그 상황에 맞는 로직에서 fetch join을 사용하면 n+1 문제를 해결할 수 있다.

⇒ 넓게 보면 N+1 문제는 즉시 로딩 뿐만 아니라 지연 로딩에서도 발생 가능하며, 지연 로딩 시 페치 조인을 통해 N+1 문제를 해결할 수 있다!


Reference

seungriyou commented 9 months ago

벌크성 수정 쿼리: JPQL과 em.flush()

다음과 같은 테스트 코드에서, 스프링 데이터 JPA(JPA)에서 제공하는 save()와 벌크 연산(= JPQL) bulkAgePlus()에서의 em.flush() 동작에 대해 알아보겠다.

@Test
public void bulkUpdate() {
    // given
    memberRepository.save(new Member("member1", 10));
    memberRepository.save(new Member("member2", 19));
    memberRepository.save(new Member("member3", 20));
    memberRepository.save(new Member("member4", 21));
    memberRepository.save(new Member("member5", 40));

    // when
    int resultCount = memberRepository.bulkAgePlus(20);
  1. 스프링 데이터 JPA(JPA) 에서 제공하는 save() 호출 시

    save() 메서드는 주로 엔티티를 영속성 컨텍스트에 저장하는 데에 사용되기 때문에, 이후 트랜잭션이 커밋되거나 명시적으로 flush()를 호출할 때까지 DB에 반영되지 않는다.

  2. 벌크 연산(= JPQL) bulkAgePlus() 호출 시

    bulkAgePlus() 메서드 내에서 JPQL을 사용하는 벌크 연산을 실행하면, JPA는 이를 DB에 즉시 반영하기 위해 flush()를 호출한다. 즉, 영속성 컨텍스트를 무시하고 벌크 연산이 직접 DB에 적용된다.

따라서 벌크 연산 후 clear()를 호출하는 것은 영속성 컨텍스트와 DB 간의 일관성을 유지하기 위해 중요하다. 벌크 연산은 영속성 컨텍스트에 있는 엔티티 상태를 업데이트하지 않으므로, DB에는 변경 사항이 반영되어 있지만 영속성 컨텍스트는 그 변경 사항을 모르는 상태가 된다. 그러므로 clear()를 호출하여 영속성 컨텍스트를 초기화하고 필요한 경우 다시 로드하여 일관성을 유지해야 한다.


Reference

seungriyou commented 9 months ago

임베디드 타입 vs. @MappedSuperclass

강의에서는 createdDateupdatedDate와 같은 변수를 임베디드 타입으로도, @MappedSuperclass 로도 정의할 수 있었다.

그 둘의 차이는 무엇이고, 실무에서는 어떤 것을 사용할까?

구분 차이점
임베디드 타입 위임
@MappedSuperclass 상속

이런 경우에는 상속을 사용하는 것이 더욱 편리하다.

예를 들어, 임베디드 타입으로 만들게 되면 다음과 같다.

class TraceDate {

  TYPE createdDate;

  TYPE updatedDate;

}

이러한 경우, JPQL 쿼리를 하려면 항상 임베디드 타입을 한 번 거쳐야 한다.

select m from Member m where m.traceDate.createdDate > ?

하지만 @MappedSuperclass 상속을 사용하면 다음과 같이 간단하다.

select m from Member m where m.createdDate > ?

이러한 편리함과 직관성으로 인해 상속을 주로 사용한다!


Reference

seungriyou commented 9 months ago

게시글 조회수 로직과 updatedAt

글을 조회하면 조회수가 1 올라가는 로직이 존재한다고 가정하자.

이때, JPA Auditing을 사용하고 있다면, 조회수가 오를 때마다 lastModifiedDate 필드도 함께 업데이트 되게 된다.

이러한 경우에는 updateAt이 변경되어야 할 비즈니스 로직에서만 따로 업데이트 해주어야 한다.


Reference