[TIL] JPA best practice 알아보기1

Kim yohan·2024년 9월 23일
0

TIL

목록 보기
19/20

우연히 JPA best practice를 발견하게 되었다!
그래서 이 레포를 통해 내 부족한 부분을 채우기 위해, 궁금점들을 해소하고자 한다.


Entity 만들때, Date 형식에 @Temporal 쓰는 이유

  • 날짜와 시간 데이터를 데이터베이스 컬럼과 매핑할 때 사용
  • 데이터베이스 상의 정확한 데이터 유형을 유지할 수 있다
  • LocalDate, LocalDateTime 사용할 때는 생략 가능
  • 종류
    • TemporalType.DATE : 시간 무시
    • TemporalType.TIME : 날짜 무시
    • TemporalType.TIMESTAMP : 시간, 날짜 모두 포함한 타임스탬프

생성날짜, 수정날짜 값 설정 못하게 하고, @CreationTimestamp, @UpdateTimestamp 사용함

  • @CreationTimestampINSERT 쿼리가 발생할 때, 자동으로 시간 넣어줌
  • @UpdateTimestampUPDATE 쿼리가 발생할 때, 자동으로 시간 넣어줌
  • 예시
@Entity
@Getter
@Builder
public class Post {

    ...

    @CreationTimestamp // INSERT 시 자동으로 값을 채워줌
    @Column(name = "created_at")
    private LocalDateTime createdAt = LocalDateTime.now();

    @Column(name = "updated_at")
    @UpdateTimestamp // UPDATE 시 자동으로 값을 채워줌
    private LocalDateTime updatedAt = LocalDateTime.now();
}

@NoArgsConstructor(access = AccessLevel.PROTECTED)의 범위?

  • JPA는 기본적으로 빈 객체 생성자를 요구해서, @NoArgsConstructor를 사용해야한다.
  • Private을 사용하면, 프록시 객체 생성시 문제가 발생한다!
    • 지연 로딩 사용 시, 실제 엔티티가 아닌 프록시 객체를 통해서 조회한다
  • Public을 사용하면, 무분별한 객체 생성이 이루어질 수 있고, Setter를 사용한 값 변경이 일어날 수 있다!

Parameter에 final 붙이는 이유?

  • 값의 재할당을 막아, 인자로 들어온 값에 대해 정합성을 유지할 수 있다.
  • 예측 가능한 코드가 된다.
  • 단점
    • 코드가 길어져 가독성을 해칠 수 있다.
    • -> 매개변수 3개 이상이면, DTO로 만들면 되어서 괜찮다

    ⛑️ final은 불변인가?
    final은 재할당을 막는 거지, 불변성을 보장하지 않는다!!!

    • Parameter에 final을 붙여도, 해당 객체의 내부 함수를 통해 필드 값을 변경할 수 있음
    • final로 선언된 Collection에 값을 추가하고, 수정할 수 있음

    예외 : Primitive 타입은 불변성이 보장된다!
    참조값이 없고, 리터럴을 참조하기 때문

    ⛑️ 그렇다면, 불변을 보장하는 방법은?

    • 객체
      • 생성자를 통해서만 값을 주입받는다.
      • 필드 값을 바꾸는 메소드가 존재하지 않게 한다.
    • 컬렉션
      • Unmodifiable Collection을 사용한다
      • 수정을 시도할 시 UnsupportedOperationException 발생

데이터베이스 DML?

데이터베이스 명령어 종류를 말한다.
명령어 종류를 표로 정리하면 다음과 같다.

명령어 종류의미명령어
DML(Data Manipulation Language)데이터 조작어SELECT, INSERT, UPDATE, DELETE
DDL(Data Definition Language)데이터 정의어CREATE, ALTER, DROP, RENAME, TRUNCA, TE
DCL(Data Control Language)데이터 제어어GRANT, REVOKE
TCL(Transaction Control Language)트랜젝션 제어어COMMIT, ROLLBA, CK

ParallelStream?

병렬 처리를 통해, 성능을 개선할 수 있다.
ForkJoinFramework 관리 방식을 사용해 작업들을 분할하고, 합친다.
잘못 사용하면, 장애가 발생할 수 있고, 성능 저하로 이어질 수 있어서 주의하며 사용해야 한다.

Fork/Join Framework


작업들을 가능한 만큼 쪼개고(Fork), Work Thread를 통해 작업 후, 결과를 합치는(Join) 방식이다.
AbstractExecutorService 클래스를 확장한 ForkJoinPool 클래스가 핵심!

ForkJoinPool

Work-Stealing 매커니즘을 사용
inbound queue에 task를 submit 해두면, 스레드들이 take하고, 스레드의 queue에 task가 없으면 다른 스레드의 task를 steal한다.
따라서, 스레드가 놀지 않고 최적의 성능을 낸다.

⛑️ 주의 사항

  1. Thread Pool 공유 문제
    별도의 설정이 없다면, 모든 parallelStream이 하나의 Thread Pool을 공유함
    blocking I/O가 발생하는 작업처럼 Thread들이 점유중인 상태가 길어지면,
    다른 Thread가 스레드를 얻을 때까지 기다리게 되면서 문제가 발생!!
    -> 해결 : 각 parallelStream마다 커스텀하여 독립적인 Thread Pool을 사용하게 한다!

  2. Custom Thread Pool 사용시 Memory Leak 문제
    parallelStream마다 독립적인 Thread Pool 사용시, OutOfMemoryError가 발생할 수 있다.
    default인 Common JoinForkPool은 static이기 때문에, 메모리 누수가 발생하지 않음
    But, Custom JoinForkPool 객체는 참조 해제되지 않거나, GC로 수집되지 않을 수 있다!!!
    -> 해결 : Custom JoinForkPool을 사용 후, 명시적으로 종료해준다!

  3. 성능 저하
    stream 연산으로 sorted(), distinct() 같이 사용시 성능 개선의 효과가 미미할 수 있다!
    이유는 이런 함수는 내부적으로 상태에 대한 변수를 공유하고, 동기화 작업을 통해 안전하게 유지하기 때문에 성능 개선이 안될 수 있다.
    이런 경우나 대상 요소의 수가 적을 경우, fork, join, context switching 비용때문에 오히려 성능 저하로 이어질 수 있다!!!

  4. 동시성 문제
    여러 스레드가 같은 데이터에 접근해 수정할 때, 동시성 문제가 발생한다.
    이때 iterator가 내부의 expectedmodCount와 ArrayList의 modCount를 비교하고, ConcurrentModificationException을 발생시킨다.

  • 해결
    • synchronized 키워드 사용
      • 다소 성능이 느려지지만, 안정성을 위해 사용하는 것이 좋다!
    • Lock 사용
      • ReentrantLock을 통해 synchronized 보다 디테일한 설정을 할 수 있다.
      • 하지만, 스레드 풀 생성을 방해하여 병렬처리하는 의미를 떨어트릴 수 있다.
    • Thread-Safe한 자료구조 사용 !참고!
      • ConcurrentHashMap, synchronizedList 등을 사용한다.
    • 결과를 새로운 곳에 저장
      • 비교적 성능은 떨어진다.
  1. 처리 순서
    처리 순서를 보장하지 않는다
    따라서, 처리결과가 순서에 의존하지 않고, 요소가 독립적인 경우에만 사용해야 한다.

결론

따라서, 분할이 잘 이루어질 수 있는 데이터 구조 혹은 작업이 독립적이면서 CPU 사용이 높은 작업에 적합하다.
데이터 양과 병렬화로 인한 오버헤드를 고려하여 병렬 처리를 사용하는 것이 중요하다!!

SynchronizedMap과 ConcurrentHashMap 비교

특성SynchronizedMapConcurrentHashMap
락 메커니즘전체 맵에 단일 락 사용부분 락(버킷/세그먼트별로 락 사용)
읽기 성능락이 걸리므로 느림락 없이 읽기 가능 (빠름)
쓰기 성능모든 쓰기 작업에서 맵 전체에 락다른 버킷에 동시에 쓰기가 가능 (빠름)
동시성 제어적은 스레드에서 간단하게 동작많은 스레드가 동시에 접근해도 성능이 좋음
사용 사례동시성 요구가 적고, 간단한 동기화가 필요한 경우높은 동시성을 요구하고, 읽기 작업이 많은 경우

일반적으로 멀티스레드 환경에서 ConcurrentHashMap을 사용하는 것이 더 좋다!


BindingResult?

  • Spring이 제공하는 Validation 처리 객체로, 검증 오류를 보관한다.
  • @ModelAttribute 객체에 binding이 실패할 경우, FieldError를 BindingResult에 담는다.
  • FiledError, ObjectError 있음
  • 직접 Error 생성해 BindingResult에 담을 수 있음
  • BindingResult 없으면
    • 400 오류 발생
    • Controller 호출되지 않고, Error Page로 이동
  • BindingResult 있으면
    • BindingResult에 오류가 담기고, Controller 정상 호출
profile
꾸준히 성실하게

0개의 댓글