우리가 서비스를 구현하면서 고려해야하는 것이 무엇이 있을까?
처음에 스타트업을 시작한다고 생각해보자.
처음에서는 프론트엔드 백엔드 DB 하나씩 쓸 것이다.
그 이후 사람들이 몰리면 백엔드와 DB를 늘리게 되는데,
백엔드는 저장하는 데이터가 없으므로 컴퓨터만 늘리면 되는데,
DB는 컴퓨터를 늘릴 때 데이터도 동기화해야하는 문제가 있다.
따라서, 백엔드에 배해 많이 늘릴 수 없다.
벡엔드가 많아지면 DB서버의 부담이 가중되니 DB서버 성능에 관심을 가져야한다.
그러면 개발을 하면서 DB 서버와 관련된 문제들을 살펴보자./
ORM 기술을 사용하면 항상 N+1 문제가 발생한다.
꼭 나쁜 것만은 아니다.
왜 생기고, 어떻게 해결해야하는가. 해결 방법에 정답이 있는 것이 아니다.
상황마다 적절한 방법들이 있다.
문제
상품 목록을 조회할 때, 상품들을 가져오고, 그와 관계를 맞은 데이터를 가져오기 위해 상품의 갯수(N)만큼 추가로 쿼리문이 실행된다.
왜 JOIN를 쓰지 않았을까?
문제가 생기는 이유.
ORM기술들이 데이터를 가져올 때 관계를 맺은 데이터들이 필요없는 경우가 있기 때문에 데이터가 사용이 될 때 가져오도록 만들었기 때문이다.
ex) 로그인을 하기 위해서 유저의 아이디 패스워드만 불러오면된다.
해결방법
사용방법
JpaRepository를 상속받은 인터페이스에서 쿼리문을 자동으로 만드는데,
우리가 직접 만들어 줄 수 있다.@Query("SELECT u FROM USER u JOIN u.userImage") // 실행할 JQPL쿼리문을 입력하는 어노테이션(SQL문 아님!!) public List<User> findAll(); //오버라이딩
관계가 맞어져 있는 대부분의 테이블이 문제가 발생하기 때문에 일일이 찾아야한다.
페이징 기능은 어떻게 구현되어있고 어떻
QueryDSL (어려움. 추가적인 기능을 지원함.)
사용 방법
- QueryDSL 라이브러리 다운로드 (apt, jpa 2개)
(https://mvnrepository.com/artifact/com.querydsl/querydsl-apt/5.0.0)
(https://mvnrepository.com/artifact/com.querydsl/querydsl-jpa)- 플러그인 추가 (https://github.com/querydsl/apt-maven-plugin)
- apt:process 실행
- target file 아래에 Qclass들이 생성되었는지 확인
- Qclass들이 자바 파일로 인식되도록 설정이 필요
(인텔리제이 기준)
Project structure에서 Project Setting - Modules - target아래있는 classes.java를 우 클릭 후 설정 변경.
- 새로운 레포지토리 인터페이스 생성
- public List findList() 매소드 추가
- 인터페이스의 구현체를 만든다.
QuerydslRepositorySupport 상속받는다.
생성자에 super(엔티티 클라스.class); 추가
QClass들 객체를 생성
쿼리문을 나타내는 매소드를 이용해 쿼리문을 작성한다.- 기존에 쓰던 Repo 인터페이스에 상속시킨다.
페이지 기능 만들기
public Page findList(Pageable pageable) 매소드 추가
만든 쿼리 문에 offest(pageable.getOffset) 과 limit(pageable.getPageSize)를 사용한다.
Page는 리스트가 아니기 때문에 처리를 따로 해줘야한다.
products.getContent() 로 리스트 반환 가능.
join 후 에 fetch join를 해줘야한다. 안그러면 N+1문제가 그대로 발생한다.
###쿼리문이 동시에 실행되야하는 것들이 있다.
상품의 정보를 DB서버에 저장하는 것
S3서버의 상품의 이미지들을 저장하는 것
1번이 저장됬는데, 2번이 에러가 나면 저장하는 안되는 데이터가 저장된 경우.
2번을 실행하면서 중간에 에러가 난 경우 모든 사진을 업로드 된 것이 아니다.
이런 경우를 원자성이 지켜지지 않았다고 표현이 되는데
해결하기 위해 @Transactional를 매소드 위에 달아두면 된다.
좋아요 기능을 구현할 때, 동시에 좋아요 요청을 받은 경우 누락되면 안된다.
이런 경우를 '격리성을 지켜야한다'라고 한다.
@Transactional과 함께 Lock 설정을 해야한다.
Lock 설정 방법
repo 인터페이스의 매소드 위에 @Lock를 쓰고
Lock 의 종류를 정해주면 된다.
비관적 Lock : 안전하지만 느리다. (SQL 의 For update와 같음.)
매소드가 실행 중일 때, 다른 스레드들이 읽지도 쓰지도 못한다.
사용방법 어노테이션 1개
@Lock(LockModeType.PESSIMISTIC_WRITE)
public void method(){} // 매소드에 붙이는 어노테이션
낙관적 Lock : 누락이 생기면 예외를 터트려 버린다.
자신이 데이터를 사용하기 전 버전을 기록해두고
사용 후에 버번이 달라져 있으면 예외를 터트린다.
사용 방법 어노테이션 2개
@Lock(LockModeType.OPTIMISTIC)
public void method(){} // 매소드에 붙이는 어노테이션
@Version
private Integer asd; //Integer, Date 클라스에만 붙이는 어노테이션
알아두면 좋은 트랜잭션의 특징 ACID
- 원자성 (Atomicity) : 트랜잭션 내의 sql문이 모두 실행되거나 모두 실행되지 않아야한다.
- 일관성 (Consistency) :
- 고립성 (Isolation) :
- 영속성 (Durability) :
알아두면 좋은 동시에 코드를 실행시키는 방법