Lewis's Tech Keep

[우아한 테크캠프][PRO][3기] 2주차 정리 - 1 본문

Java/우아한 테크캠프 정리

[우아한 테크캠프][PRO][3기] 2주차 정리 - 1

Lewis Seo 2021. 11. 13. 02:52

2주차 내가 만드려고 하는 것 : JPA 기반 백엔드

  중복으로 들어간 createdAt, updatedAt을 보다가 예전에 이동욱님의 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 에서 봤던 Auditing을 한번 써 보면 좋을 것 같아서 써 보았다.
하지만 책의 좋은 소스를 쓰레기처럼 활용했다는 생각이 든 한 주다.

  써보면서 확실히 느낀게 나는 JPA의 Core Concepts, 왜 JPA가 현재 트렌드가 되어가고 있는지 알지 못한다는 것이었다. 

[JPA Core Concepts]

[JPA에 관한 기본적인 개인적으로 납득이 간 좋은 이유 - 이영한님]

이번 주에 내가 집중적으로 학습하고자 했던 것은 JPA Core Concepts, 영속성 컨텍스트, Auditing 이다.

 

 

Study List


Jpa Auditing (Spring Data JPA Auditing)


 

Auditing은 감사 (회계 감사할 때 감사)의 뜻으로 Spring Data JPA 에서 가져오는 옵션이다.

Spring Data provides sophisticated support to transparently keep track of who created or changed an entity and the point in time this happened. To benefit from that functionality you have to equip your entity classes with auditing metadata that can be defined either using annotations or by implementing an interface.

문서에서의 정의와 같이 엔티티를 생성하거나 변경하는 것에 대해 이러한 상황이 발생한 그 시점(the point)에 대해 투명하게 추적할 수 있는 지원(기술?)이다. 이 기능을 사용하기 위해서 엔티티 클래스나 에서 annotation을 이용하거나 interface를 구현받아 정의한 anditing 메타데이터가 필요하다.

Spring Data JPA ships with an entity listener that can be used to trigger capturing auditing information. So first you have to register the AuditingEntityListener inside your orm.xml to be used for all entities in your persistence contexts

Spring Data JPA는 엔티티 리스너(auditing 정보 캡쳐를 트리거 시킬 수 있는)와 함께 제공된다. xml로 할 경우 xml 내에 Listener를 등록하면, 해당 영속성 컨텍스트의 모든 엔티티에 사용할 수 있게 된다.

 

JPA Auditing은 @EnableJpaAuditing 어노테이션을 application 객체 위에 설정하고, 
@MappedSuperclass 어노테이션으로 상속 관계에서 Element들이 매핑이 되게 만들고,

@EntityListener에서 AuditingEntityListener 안에 있는 @PrePersist와 @PreUpdate 어노테이션이 붙어있는 메서드들에 의해 Auditing(감사)를 한다. (= 발생한 시점부터 계속 추적해 주는 것)

 

 

 

JPA 영속성 컨텍스트


 

JPA 영속성 컨텍스트를 하고 가장 많이 본 관련 그림이다.  구조를 이해하는 데 도움이 된다.

 

JPA 영속성 컨텍스트는 4가지 상태를 가진다.

비영속(new/transient): 처음 생성 (영속성 상태가 아님 아예)

영속(managed): persist()에 의해 managed 상태가 되며 영속성 컨텍스트에 저장된 상태

준영속(detacted): detach() 와 같은 것들에 의해 영속성 상태에 저장되었다가 분리된 상태

삭제(removed): remove()로 삭제된 상태

 

flush()로 db에 쿼리들을 보낸다.

 

영속성 컨텍스트의 특징들

1차 캐시

jpa는 바로 db에 저장되는 것이 아닌 영속성 컨텍스트 상태로 1차 캐시에 먼저 저장된다.

이는 hashMap의 구조와도 비슷한데 Id를 키로 가지고 있어서 Id로 찾을 때 1차 캐시에서 검색 후 있다면 바로 반환해준다.

동일성 보장

해당 반환된 객체는 db에서 온 새로운 객체가 아니고 저장되어있는 객체기 때문에 주소가 같다. 동일성을 보장한다.

트랜잭션 쓰기 지연

save 한다고해도 바로 되는 게 아니고 쓰기를 지연할 수 있는 타이밍 까지 최대한 늦춤

변경 감지

jpa update를 따로 하지않는다. set으로 등록하고 데이터를 변경하면 어느 시점에서 1차 캐시에 저장되어있는 snapshot이 있는데 이 snapshot의 데이터와 비교후 변경되는 부분은 update를 해준다.

지연 로딩

jpa는 프록시 객체를 만듦으로써 DB에서 참조하는 객체들의 데이터를 가져오는 시점을 정하는데 Lazy-loading을 지원함으로써 이 과정을 최소화 해 준다. (물론 fetch type = eager 일 때, Id가 autoIncrement 와 같이 바로 들어가야 하는 경우 처럼 바로 적용 시킬 수 있는 옵션이 있다.) [Lazy와 Eager에 대하여]

또한 객체를 실제로 사용하는 시점까지는 쿼리를 발생 시키지 않는 것이다.

 

 

 

JPA 연관 관계 매핑


객체는 각 관계가 독립적이지 않다. oop 설계시 고려할 점에 역할, 책임, 협력이 있다. 객체가 만들어 질 때 의미있는 단위의 상태와 행동을 가지는 객체로 정의하고 이를 역할, 책임, 협력에 따라 설계한다면 객체 간에 연관 관계가 생기는 일은 현재로써는 불가피하다는 말에 일부 동의한다. (물론 oop의 concept을 공유하며 설계했을 때의 얘기라고 생각한다.)

객체는 독립적인 존재가 아니라 기능을 구현하기 위해 협력하는 공동체의 일원이다.
[오브젝트 - 코드로 이해하는 객체지향 설계]  

하지만 db에 있는 콜렉션이나 테이블들 간에는 연관 관계라는 것이 없다. 하지만 JPA는 ORM 으로 이 연관 관계가 존재하는 객체연관 관계가 존재하지 않는 테이블 간에 조절해야 할 것이 생긴다. 이를 위해 나온 것이 연관 관계 매핑이다.

 

일대일 단방향 시에는 자주쓰는 것이 연관 관계의 주인이다 (외래키 생성자)

 

다대일 양방향, 다대일 단방향으로 설정하는 것을 추천 (외래키가 Many 쪽에서 관리되기때문에 이슈가 적다)

 

다대다 양방향은 일대다 <- 일대일(공유) -> 다대일 양방향으로 풀어내는 것이 좋다. (공유하는 일대일 관계를 가시화)

그 이유는 다대다 양방향만 설정하게되면 자동으로 관계테이블이 생성되는데 이 테이블이 스키마 변경에 취약하기 때문


객체 연관 관계
  station을 통해 line 접근 가능 station -> line  
  line을 통해 station 접근 불가능 line -> station  
  연관관계 주인 : station   
  line쪽에서도 station에 접근이 가능, station 쪽에서도 line에 접근이 가능 
양방향 관계라고 말할 수 있다.
  
oneToMany 단방향의 many 쪽은 db에 접근 해보기 전까지 내가 외래키 관리자 인지 모른다.

때문에 update 쿼리가 일어난다. (ManyToOne에서는 외래키를 가지고 있기 때문에 괜찮다)
  
다대일 관계에서 관계 테이블을 만들 필요가 없을 때 oneToMany 쪽에서 mappedBy를 통해 관계 테이블을 생성하지 않을 수 있다. mappedBy 추가하지 않는다면 관계 테이블이 생김 (line_stations)
  
외래키를 다루는 쪽이 연관 관계의 주인이다. (=외래키 관리자)
외래키 다루는 쪽에서는 외래키 등록 조회 수정 삭제와 같은 일들이 다 가능하다.

외래키를 다루지 않는 쪽이라면 키 조회만 가능하다.

 

단방향 연관관계 일 경우 연관관계의 주인을 설정하는 것이 중요하다.

OneToMany 단방향에서 JoinColumn 은 나는 관계 테이블을 쓰지 않겠어 라는 뜻이다.

 

oneToOne 단방향에서 lineStation(중간 매개자 같은 느낌으로 생각 중)에서 외래키를 가지면 oneToMany 이 될 때 테이블 구조를 그대로 가져갈 수 있다. (=변경하지 않아도 괜찮다)

OneToOne 단방향에서 뒤의 one은 column 이름의 역할만 한다. (fkey 자체가 앞의 one에만 있기 때문)
 

외래키 관리자가 아닌 쪽에서 add나 set을 하면 외래키의 관리자가 추가를 안하면 null이 들어가 연결이 되지 않는다.

 

 

 

 

 

읽어봐야 할 것 : JSR338 (JPA 표준 상세)

JPA 빈 생성자가 필요한 이유(No args Constructor) (=> 하지만 필수는 아니다.)


메인 이유 : 런타임 오버헤드를 최소화 하기 위함. & JPA 스펙이 요구한다. (JSR 338을 찾아봐야 할 듯)

 

궁금한 점


  • 외래키 constraint 를 보면 예제랑 키 값이 똑같은데 왜 그럴까?
  • Casecade가 무엇인지
  • 고아객체가 무엇인지 ( 관계가 끊어졌을 때의 관리에 대한 얘기 부모 객체가 끊어지면 -> 자식 객체도 끊어져야 함)

 

Comments