인프런 스프링 강의로 유명한 김영한님의 실전! 스프링 부트와 JPA 활용1편을 공부하고 간단히 후기를 남겨보려고 합니다.
[목차]
[관련 포스팅]
1. 강의 느낀점
드디어 JPA 활용2 강의를 다 들었습니다.
예전에 들었을 때 활용1까지 이해도가 낮은 상태라 2를 도전하지 못했는데 이번에 완강할 수 있어서 좋았습니다.
2에서도 새로운 개념이나 주의사항, 고려해야할점 등을 많이 소개해주셔서 시간이 아깝지 않은 강의였네요.
내년 목표 실적을 설정할 때 JPA와 관련된 기능 추가하거나, 토이프로젝트를 꼭 해봐야곘습니다.
방대한 내용이니만큼 실무나 토이프로젝트 등에 적용하지 않으면 많이 휘발될 것 같습니다.
이번 포스팅에서도 강의를 들으며 기억해야할 점이나 느낀 점 등을 정리해보려고 합니다.
2. 강의에서 기억할 것
이번 강의는 JPA를 통해 웹쇼핑몰 샘플을 만드는 것이 아니라 API를 만드는 과정을 통해 강의를 진행했습니다.
활용1보다 당연히 난도가 있는 것 같고 기초강의와 활용1강의를 제대로 들어야 무난하게 활용2도 이해할 수 있을 것 같습니다.
저도 100% 이해한 것은 아니지만 휘발되기 전에 최대한 간결하게 중요한 부분 위주로 정리해보려고 합니다.
- API 응답은 엔티티로 그대로 노출하면 안 된다. DTO에 담아서 반환하는 것이 좋다. API스펙은 바뀔 수 있기 때문에 엔티티에 섞어 넣으면 추후 유지보수가 힘들다.
- 수정시에는 merge()보다 더티체킹(변경감지)를 활용하자.
- List로 바로 반환시 배열(jsonArray)로 나간다. 이러면 count 등을 추가할 수가 없다. Result와 DTO를 만들어 반환하자.
- 엔티티를 직접 리턴시 양방향 연관관계일 경우 한 곳을 @JsonIgnore해야 무한루프가 돌지 않음
- 1+N문제 해결을 위해 xToOne관계만 join fetch로 우선 가져오자.(LAZY 무시후 EAGER모드로 작동)
- JPA에서 바로 DTO로 받을 수도 있다. JPQL에서 select절에 new 패키지경로.dto명(값,값..)으로 원하는 값만 바로 가져올 수 있어서 API스펙 최적화된 JPQL을 만들 수 있다.
- 그러나 엔티티를 받은 후 DTO로 변환하는 것을 더 권장한다. 재사용성이 더 좋고 원하는 select절만 받는 게 성능에 그렇게 크게 영향을 주진 않는다.(엄청 큰 데이터가 아니라면)
- 1:N관계를 JPQL로 페치 조인하면 뻥튀기될 수 있다. distinct 처리가 하이버네이트6부터 되긴 하지만..
- ORDER-ORDER_ITEM의 1:N 관계 테이블을 가정하고 ORDER에 2개, ORDER_ITEM에 4개의 데이터가 있어서 ORDER를 기준으로 JOIN하면 4개가 나온다.
- 페이징 처리는 쓰면 안됨(DB 1:N중 N을 중심으로 페이징 계산을 하기 때문). 그리고 메모리에 올려서 페이징을 한다. 메모리에 1만개를 올려서 페이징을 한다면 OutofMemory발생 가능성.
- 위와 같이 1:N관계 페치조인으로 한계가 있다. 이를 해결하려면?
- 우선 xToOne관계(N:1 or 1:1)만 fetch join을 사용한다. 그러면 뻥튀기 문제가 없다.
- 나머지는 for문을 통해 사용한다. 그러면 jpql쪽에서 페이징(offset, limit) 기능을 사용할 수 있다.
- 그러나 N(orderItem)쪽으로 인해 여전히 N+1문제가 발생할 수 있다.
- 이를 위해 batchSize를 사용하자.
- batchSize는 개별적용, 글로벌적용 모두 가능하다. 글로벌적은 application.yml등에 default_batch_fetch_size로 설정값을 주면 된다. 설정값을 적용하면 그 값만큼 in 쿼리 처리가 된다.
- orderItem에서는 order_id in (1,2)같이 in절로 한방에 조회가 된다.
- 1:N DTO조회 역시 먼저 xToOne을 fetch join으로 가져오고, N은 for문을 통해 jpql로 또 가져온다.
- OSIV
- Open Session In View로 Sesion은 하이버네이트 초기 시절의 EntityManager다. 즉 Open EntityManager In View와 유사한 의미다.
- 트랜잭션이 맺어지는 시점(@Transactional setAutoCommit(false))에 em과 db커넥션이 연결됨
- 일반적으로 트랜잭션 종료시점에 커넥션을 반환하지만, OSIV 옵션이 true(default)면 고객에게 반환되는 시점까지 커넥션이 유지된다.
- LAZY로딩을 트렌젝션 종료 후 할 수도 있기 때문에 이 옵션이 켜져있는 것이다. DB커넥션이 되어있어야 LAZY로딩이 가능하기 때문.
- OSIV를 키는 것은 장단점이 있다.
- 우선 DB 커넥션을 오래 물고 있을 수 있기 때문에 성능이 중요한 실시간 사용자 서비스에는 맞지 않는다.
- 어드민처럼 동시접속자가 적은 곳은 키고 사용해도 괜찮다. 이 경우 LAZY로딩 코드를 트랜잭션 범위에 넣지 않아도 된다.
- Spring Data JPA 간단 소개
- 중복발생이 많이 되는 기본적인 쿼리 메소드(findAll(), findOne(), find() 등)을 따로 인터페이스로 만들어두어 사용할 수 있는 라이브러리(아래 코드 예시 참고)
- JpaRepository<참고할 테이블, 테이블ID 타입>
- 기본적인 쿼리 메소드를 제외하고는 따로 만들어줘야 함. 그러나 룰이 있기 때문에 룰을 지키면 아래처럼 findByName같은 메소드를 만들어 쓸 수 있다.
public interface MemberRepository extends JpaRepository<Member, Long> {
//select m from Member m where m.name = :?
List<Member> findByName(String name);
}
- QueryDsl 소개
- 복잡한 JPQL을 자바 코드처럼 짤 수 있음(아래 코드 참고)
- build.gradle설정 꽤 많이 들어감..
- 설정후 compileQueryDsl을 하면 build/generated하위에 QMember, QAddress 등 전용 엔티티 생성됨
- 이후 리포지토리 등에서 사용하면 됨
- 장점
- 컴파일 시점에 문법 오류를 잡아줌
- 재사용성(리팩토링 용이)
- 동적쿼리 만들기 좋음
- 직관적
public List<Order> findAll(OrderSearch orderSearch) {
JPAQueryFactory query = new JPAQueryFactory(em);
QOrder order = QOrder.order;
QMember member = QMember.member;
return query
.select(order)
.from(order)
.join(order.member, member)
.where(StringUtils.isEmpty(orderSearch.getOrderStatus()) ? null : order.status.eq(orderSearch.getOrderStatus()), StringUtils.isEmpty(orderSearch.getMemberName()) ? null : member.name.like(orderSearch.getMemberName()))
.limit(1000)
.fetch();
}
3. 마치며
JPA강의를 다시 제대로 들어야지 라고 하면서 미루고 미루다가 올해 활용2까지 마무리할 수 있어서 다행인 것 같습니다.
요즘 사실 JPA가 대부분 자바,스프링 환경에서 필수적으로 쓰이고 있어서 레거시한 환경을 운영하는 제 입장에서는 실무에서 JPA를 아직 못쓰고 있어서 아쉬움이 많았는데, 강의를 통해 어느정도 감을 잡을 수 있었습니다.
물론 실무에 적용하면 다른 얘기가 되겠지만, JPA를 사용할 기회가 올 때 어느정도 방향성을 잡고 나갈 수 있을 것 같습니다. 내년에 회사에서 기능 적용을 작은 것부터라도 해볼 예정입니다.
감사합니다!
'개발자 일지 > JPA' 카테고리의 다른 글
[강의 후기] 인프런 김영한님 스프랑부트와 JPA 활용1 강의 후기 (1) | 2024.10.08 |
---|---|
[JPA 기초] Entity와 EntityManager 개념, 관계 (0) | 2024.09.21 |
[인프런 JPA]자바 ORM 표준 JPA 프로그래밍 정리2 (0) | 2023.01.31 |
[인프런 JPA]자바 ORM 표준 JPA 프로그래밍 정리1 (0) | 2023.01.28 |