우연히 JPA best practice를 발견하게 되었다!
그래서 이 레포를 통해 내 부족한 부분을 채우기 위해, 궁금점들을 해소하고자 한다.
@CreationTimestamp
는 INSERT
쿼리가 발생할 때, 자동으로 시간 넣어줌@UpdateTimestamp
는 UPDATE
쿼리가 발생할 때, 자동으로 시간 넣어줌@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
를 사용해야한다.⛑️ final은 불변인가?
final은 재할당을 막는 거지, 불변성을 보장하지 않는다!!!
- Parameter에 final을 붙여도, 해당 객체의 내부 함수를 통해 필드 값을 변경할 수 있음
- final로 선언된 Collection에 값을 추가하고, 수정할 수 있음
예외 : Primitive 타입은 불변성이 보장된다!
참조값이 없고, 리터럴을 참조하기 때문⛑️ 그렇다면, 불변을 보장하는 방법은?
- 객체
- 생성자를 통해서만 값을 주입받는다.
- 필드 값을 바꾸는 메소드가 존재하지 않게 한다.
- 컬렉션
- Unmodifiable Collection을 사용한다
- 수정을 시도할 시
UnsupportedOperationException
발생
데이터베이스 명령어 종류를 말한다.
명령어 종류를 표로 정리하면 다음과 같다.
명령어 종류 | 의미 | 명령어 |
---|---|---|
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 |
병렬 처리를 통해, 성능을 개선할 수 있다.
ForkJoinFramework 관리 방식을 사용해 작업들을 분할하고, 합친다.
잘못 사용하면, 장애가 발생할 수 있고, 성능 저하로 이어질 수 있어서 주의하며 사용해야 한다.
작업들을 가능한 만큼 쪼개고(Fork), Work Thread를 통해 작업 후, 결과를 합치는(Join) 방식이다.
AbstractExecutorService 클래스를 확장한 ForkJoinPool 클래스가 핵심!
Work-Stealing 매커니즘을 사용
inbound queue에 task를 submit 해두면, 스레드들이 take하고, 스레드의 queue에 task가 없으면 다른 스레드의 task를 steal한다.
따라서, 스레드가 놀지 않고 최적의 성능을 낸다.
Thread Pool 공유 문제
별도의 설정이 없다면, 모든 parallelStream이 하나의 Thread Pool을 공유함
blocking I/O가 발생하는 작업처럼 Thread들이 점유중인 상태가 길어지면,
다른 Thread가 스레드를 얻을 때까지 기다리게 되면서 문제가 발생!!
-> 해결 : 각 parallelStream마다 커스텀하여 독립적인 Thread Pool을 사용하게 한다!
Custom Thread Pool 사용시 Memory Leak 문제
parallelStream마다 독립적인 Thread Pool 사용시, OutOfMemoryError가 발생할 수 있다.
default인 Common JoinForkPool은 static이기 때문에, 메모리 누수가 발생하지 않음
But, Custom JoinForkPool 객체는 참조 해제되지 않거나, GC로 수집되지 않을 수 있다!!!
-> 해결 : Custom JoinForkPool을 사용 후, 명시적으로 종료해준다!
성능 저하
stream 연산으로 sorted(), distinct() 같이 사용시 성능 개선의 효과가 미미할 수 있다!
이유는 이런 함수는 내부적으로 상태에 대한 변수를 공유하고, 동기화 작업을 통해 안전하게 유지하기 때문에 성능 개선이 안될 수 있다.
이런 경우나 대상 요소의 수가 적을 경우, fork, join, context switching 비용때문에 오히려 성능 저하로 이어질 수 있다!!!
동시성 문제
여러 스레드가 같은 데이터에 접근해 수정할 때, 동시성 문제가 발생한다.
이때 iterator가 내부의 expectedmodCount와 ArrayList의 modCount를 비교하고, ConcurrentModificationException을 발생시킨다.
따라서, 분할이 잘 이루어질 수 있는 데이터 구조 혹은 작업이 독립적이면서 CPU 사용이 높은 작업에 적합하다.
데이터 양과 병렬화로 인한 오버헤드를 고려하여 병렬 처리를 사용하는 것이 중요하다!!
SynchronizedMap과 ConcurrentHashMap 비교
특성 SynchronizedMap ConcurrentHashMap 락 메커니즘 전체 맵에 단일 락 사용 부분 락(버킷/세그먼트별로 락 사용) 읽기 성능 락이 걸리므로 느림 락 없이 읽기 가능 (빠름) 쓰기 성능 모든 쓰기 작업에서 맵 전체에 락 다른 버킷에 동시에 쓰기가 가능 (빠름) 동시성 제어 적은 스레드에서 간단하게 동작 많은 스레드가 동시에 접근해도 성능이 좋음 사용 사례 동시성 요구가 적고, 간단한 동기화가 필요한 경우 높은 동시성을 요구하고, 읽기 작업이 많은 경우 일반적으로 멀티스레드 환경에서 ConcurrentHashMap을 사용하는 것이 더 좋다!
@ModelAttribute
객체에 binding이 실패할 경우, FieldError를 BindingResult에 담는다.