TIL

2022/11/08 TIL

mayleaf 2022. 11. 9. 00:36

JPA 프로그래밍

702 페이지까지 읽었다.

컬렉션과 부가기능 고급 주제와 성능 최적화 부분을 다 읽었고 트랜잭션과 락, 2차캐시 파트를 읽기 시작했다.

컬렉션과 부가기능

컬렉션의 경우 셋과 리스트를 이용하는 방법과 이 둘을 사용할때 hashcode와 equals 메쏘드가 어느 시점에 사용되는지 배웠다.

Order by와 Order column에 대해서 배웠고, 오더 컬럼이 프로그램쪽에서 컨트롤하는 방법, 오더 바이가 데이터베이스의 order by를 사용하는 것이라는 것을 배웠다. Converter는 typeorm에서 transformer 와 유사한 역할을 하는데, 객체에 저장한 값을 다른 값이나 형태로 매핑하여 쓸수 있게 해주는 방법이다.

데이터들을 저장할때에는 이벤트들이 발생하는데, 영속화하기전,후, 데이터베이스에서 값을 로드해온 전,후, 데이터베이스에 플러시를 하기전,후, 엔티티의 값을 업데이트하기 전, 후(플러시와는 관계없이 그냥 1차캐시 내부에서 정보가 수정된 경우도 포함)등이 존재한다는 것을 배웠고, 이 이벤트들에 대해서 처리하는 리스너를 등록하는 방법 세가지를 배웠다.

첫 번째는 리스너 클래스를 정의해서 엔티티에서 쓰도록 만드는 것이고, 두번째는 엔티티에 직접 리스너를 등록하는 방식, 세 번째는 부모 클래스의 리스너를 사용하는 것이다. 이때 리스너는 여러 리스너가 동시에 적용될 수 있으며 이 사이에는 순서가 존재한다. 부모 클래스의 리스너 -> 직접 구현 리스너 -> 엔티티에 직접 등록한 리스너 순으로 동작하는 것으로 기억한다. 

이 이야기를 보면 의도치 않은 리스너를 뺄 수는 없을까(부모 엔티티 클래스의 리스너를 특정 하위 엔티티에서는 안 쓰고 싶은 경우등)하는 생각이 들 수 있는데, 이를 위한 어노테이션들이 모두 존재한다.

쿼리를 짜다보면 A객체를 사용할때 항상 따라오는 B객체가 존재하는 경우가 있다. 이런 경우 반복적으로 조인해서 패치해야한다.(스프링 jpa 안 만져봤어도 다른 프레임워크 써봤으면 다들 똑같음). 이럴때 패치조인을 계속 붙여주는 수고를 피하기 위해서 엔티티그래프를 사용할 수 있다는 것을 배웠다.

엔티티 그래프에는 두 종류가 있는데 전자는 정적인 네임드 엔티티 그래프이고, 후자는 동적 엔티티 그래프이다.

정적 엔티티 그래프에서는 미리 @NameEntityGraph를 등록해두고 나중에 힌트에 등록해서 쓸 수 있다.

동적 엔티티 그래프는 어노테이션을 사용하지 않고 엔티티 그래프를 엔티티 매니저를 통해 생성해서 속성 노드를 추가하고 이를 힌트에 넣어주는 방식으로 사용한다.

이때 depth가 1이상인 조인을 사용하는 경우 subgraph를 통해서 문제를 해결할 수 있다.

고급 주제와 성능 최적화

예외처리 파트에서는 JPA 예외들에 대해서 볼 수 있었다. 롤백을 시키는 예외와 롤백이 필요없는 예외가 있었는데, 롤백을 시키는 예외는 중복된 영속화와 락킹 문제들이 있었다. 그리고 롤백이 필요없는 예외에는 값을 찾아봤는데 없거나하는 경우(이건 사실 예외긴한데 로직상으로는 예외가 아닌 경우로 보임)등이 있었다. 그리고 이런 예외들이 스프링 프레임워크의 JPA 예외 변환기를 통해서 스프링 사용자가 쓰기 쉽게 된다는 것을 배웠다. 그리고 트랜잭션 롤백시, 자바 객체까지 원상태로 복구해주는 것은 아니기 때문에, 그대로 다른 트랜젝션에서 사용하면 위험하다는 것을 배웠다. 추가로 이런 문제를 해결하기 위해서 스프링에서는 롤백이 발생했을때 영속성 컨텍스트를 초기화해줘서 문제를 예방한다는 것도 알게 되었다.

엔티티간의 비교는 같은 영속성 컨텍스트 내부에서는 같은 객체를 사용하기 때문에 == 연산으로 비교를 할 수 있지만, 서로 다른 영속성 컨텍스트에서는 서로 다른 객체를 사용하기 때문에 그렇게 쓸 수 없다. 만약 다른 영속성 컨텍스트를 쓴다면, equals를 통해서 비교하면된다. (데이터베이스 동등성의 경우 영속화 하기 전에는 식별자가 없어서 비교가 어렵기 때문)

프록시 쪽에서는 영속성 컨텍스트 내부 프록시 객체가 어떻게 동작하는지 배웠다.

동등한 객체를 getReference로 먼저 검색하면 프록시객체로 쭉 사용되고, find로 검색하면 객체로 사용되는 것을 배웠다.

그리고 프록시 타입의 경우 원본 엔티티를 상속받아서 구현되었기 때문에 원본객체랑 == 연산하면 같다고 알수가 없다. 그래서 동등성을 비교해야하는데 이때 클래스에 == 연산하면 상속받아 구현한거라 안되고 instanceof 연산을 써야한다는 점, 프록시가 실제 값을 받아오기 전에는 private 값에 아무런 값도 없어서 접근자로 비교해야한다는 것도 배웠다.

동일한 상속 이슈로 부모타입에 프록시 사용하는 경우도 상속 문제때문에 정확한 비교가 어려운데, 이 문제를 해결하는 방법으로 네가지를 제시했는데. 첫째는 처음부터 자식 객체를 로딩해서 가져오는 것, 두 번째는 프록시를 벗겨서 엔티티를 가져오는것(하이버네이트 만져야함), 세 번째는 인터페이스로 비교대상이 되는 값을 찾을 수 있게 분리해서 사용하는것, 네 번째는 비지터 패턴을 사용하는 것이었다. 개인적으로는 세번째 방법이 제일 좋은 것 같음.

성능 최적화에서는 N+1 문제가 발생하는 경우와 이를 해결하기 위한 방법 세가지 패치조인, 배치사이즈 어노테이션, 서브셀렉트를 배웠다.

패치는 객체를 바로 조인해서 사용하는 것, 배치 사이즈는 배치사이즈 만큼씩 where IN 절을 이용해서 가져오는것, 서브셀렉트는 서브쿼리를 이용해서 사용하는 방식이다

배치처리의 경우 주의해야하는 점이 영속성 컨텍스트에서의 메모리 관리다.

영속성 컨텍스트는 엔티티를 1차캐시에서 계속 저장해서 쓰기 때문에 배치처리한다고 객체를 엄청 많이 로딩해서 작업을 수행하다보면 메모리가 부족해지는 이슈가 생길 수 있다. 그래서 페이징처리해서 일정 간격마다 영속성 컨텍스트를 초기화해줘야한다

쿼리 힌트는 오라클밖에 아직 지원을 안해줘서 방언을 등록해서 써야한다.

트랜잭션을 지원하는 쓰기 지연은 IDENTITY 아이디를 쓰지 않는경우에 가능하다.

나머지 경우에는 읽기만 하는 경우에는 읽기전용 트랜젝션을 쓰거나, 영속성 컨텍스트에서 벗어난 상태에서 쓰거나, 트랜잭션 모드를 MANUAL로 바꿔서 변경감지와 플러시를 자동으로 하지 않도록 바꿔 더 효율적으로 돌아가게 하는 방식등의 튜닝방법을 배웠다.

 

트랜잭션과 락, 2차 캐시

트랜잭션 수준에 대해서 다시 배웠다.  네 단계로 나뉘고, 리드 언 커밋드, 리드 커밋드, 리피터블 리드, 시리얼라이저블 이렇게 네 단계가 있다.

Read uncommit 수준에서는 한 트랜젝션안에서 쓰고 있는 값을 다른 트랜잭션에서 바로 읽을 수 있다. 그래서 의도치 않게 계산중인 값을 읽어서 엉뚱한 값을 사용하거나, 아니면 비어진 값을 읽어서 문제가 발생할 수 있다.

read commit 수준에서는 커밋이 된 값만 읽을 수 있어서 항상 persisted된 값을 읽을 수 있다. 그러나 트랜젝션을 수행하는 도중에 다른 트랜젝션에서 값을 바꿔서 다시 조회했을때 다른 값을 읽어올 수 있는 이슈가 있다.

repeatable read에서는 이런 로우의 변경에 대해서 lock을 걸기 때문에 한 트랜잭션에서 반복해서 읽어도 동일한 값을 읽을 수 있다.

마지막으로 serializable단계에서는 테이블 전체에 쓰기 락을 걸어서 팬텀리드 이슈를 없애준다. 팬텀리드의 경우는 트랜젝션이 커밋되기 전에 다른 트랜젝션이 해당 테이블의 다른 로우를 추가하거나 쓰게 되어서  반복 조회시 결과 집팝이 달라지는 것이 팬텀 리드이다. serializable 단계에서는 이런 팬텀리드를 없애기 때문에 더 안전하지만, 동시성이 많이 떨어져서 성능 이슈가 발생 할 수 있다.

 

낙관적 비관적 락은 이제 막 읽기 시작해서 내일 다시 읽어봐야겠다.

 C레벨의 탄생

183페이지 까지 읽었다.

시간이 지날 수록 사람들이 기대하는바가 많고, 초기일수록 관대하고 쓸 수 있는 자원이 많기 때문에 이런 자원들을 처음에 많이 사용해야한다는 점, C레벨이 되어서 다른 동료들과 점차 고립되어가기 쉬운 환경에 처할때 더더욱 팀원들과 더 의사소통을 많이하고 그럴 수 있는 환경을 조성해야한다는 것을 배웠다.

 

엘라스틱 서치

엘라스틱 서치랑 키바나 로컬컴에 설치해서 쓰기 시작했다.

docker compose로 작성해서 쓰는데, 문서가 너무 잘되어있어서 처음 써보는 사람도 쉽게 접근할 수 있는 것 같다.

처음 엘라스틱 서치를 썼던 버전이 5.6이었는데 그때는 도커 문서가 이렇게 잘 되어있지도 않았고 elasticsearch.yml을 노드별로 작성해서 넣어주고 unicast로 서로 바라보게 해줘야해서 힘들었는데, 한방에 이렇게 작성할 수 있으니 참 편한 것 같다.

그리고 그것과 별개로 도커 설정중에 모르는게 많아져서 좀 공부좀 해봐야겠다

volumes에 드라이버 지정하는 거 부터 봐야겠다.

 

책읽은 내용 오늘 처럼 꼼꼼하게 적으면 더 좋은 것 같다. 기억을 꺼내면서 다시 공부하는 느낌이다.

'TIL' 카테고리의 다른 글

2022/11/03 TIL  (0) 2022.11.04
2022/11/01 TIL  (0) 2022.11.01
2022/10/31 TIL  (0) 2022.10.31
2022/10/29 TIL  (0) 2022.10.29
2022/10/28 TIL  (0) 2022.10.29