PPAK

[Spring/JPA] JPA 란? (ORM/Persistence Context) 본문

spring/jpa

[Spring/JPA] JPA 란? (ORM/Persistence Context)

PPakSang 2022. 7. 2. 01:25

보편적으로 서비스가 구동되는 과정에서 데이터의 최종 저장소는 데이터베이스이다.

 

그 중에서도 관계형 데이터베이스는 우리가 보편적으로 사용하는 데이터베이스 모델이며 키(pk)를 통해 값을 조회하는 방식을 가진다.

 

자바 진영에서는 JDBC API 를 통해서 데이터베이스와의 직접적인 연결관계를 구축한다.

 

JDBC 예제 코드를 살펴보면 connection 을 생성하고, SQL 문을 전송하여 데이터를 송수신 하고, connection 을 닫는 형태를 가지는데, 개발자가 매번 데이터베이스에 접근하려 할 때마다 위와 같은 중복된 로직을 작성하는 것은 굉장히 비효율적일 것이다.

 

이와 같은 반복되는 로직을 줄이고자 SQL Mapper, ORM 등과 같은 기술이 탄생하였는데, 오늘은 ORM 의 개념을 담고있는 JPA 에 대해서 알아보도록 하겠다.

JPA

JPA 는 Java Persistence API 의 약자로 자바 진영의 ORM 기술 표준을 가리킨다.

 

ORM 은 자바의 객체와 같은 데이터를 관계형 데이터베이스 테이블에 매핑하는 기술을 의미한다.

 

JPA 는 이 ORM 기술과 더불어 자바 코드로 작성한 로직을 적절히 SQL 문으로 바꿔주는 역할을 가진다.

 

JPA 는 단순 인터페이스(기술 표준) 에 불과한데, 이와 같은 인터페이스를 실질적으로 구현하는 기술이 Hibernate 이다.

 

위와 같은 객체와 데이터베이스 테이블을 매핑하는 기술이 필요한 이유는 아래와 같다

 

SQL 중심의 개발을 피할 수 있다

데이터베이스와 데이터를 주고 받기 위해서는 어쩔 수 없이 SQL 문 작성을 피할 순 없다.

 

하지만 객체의 필드가 추가되는 등의 테이블에 변화가 있을 때마다 기존에 작성하였던 SQL 문에 새로 추가된 필드를 반영하는 SQL 문을 개발자가 매번 추가하는 것은 여간 힘든일이 아니였다.

 

그래서 JPA 는 개발자가 작성한 자바 코드를 자체적으로 SQL 문으로 바꿔주는 역할을 수행하는데, 이를 통해서 개발자는 직접 SQL 문을 작성할 필요가 없어졌고, 이전보다 더 직관적인 형태의 자바 코드를 작성할 수 있게 되었다.

 

패러다임의 불일치를 해소한다

기본적으로 RDB 와 객체지향 모델은 데이터를 관리하는 방식이 다르다. 객체지향 모델은 데이터의 재활용성에 초점을 맞추고 있는 반면에

RDB 는 데이터를 테이블 단위로 정형화 하는데에 초점이 맞춰져 있다. (패러다임이 불일치한다)

 

RDB 에는 객체 지향에서 추구하는 상속, 다형성, 추상화 등의 기술을 별도로 지원하지 않기 때문에 개발자가 직접 알맞는 쿼리문을 작성해줘야한다.

 

이것을 패러다임의 불일치를 해소하는 것이라고 하는데, 개발자가 매번 불일치를 해소하기 위해 코드를 작성하는 것은 실수를 유발할 위험이 있고 이 때문에 매우 피곤한 작업이 될 수 있다.

 

보통의 패러다임의 불일치는 일정한 패턴을 통해 해소가 될 수 있고, 패턴이 존재하기 때문에 JPA 가 이 부분을 해결하는 역할을 내포하고 있고, 개발자는 기존의 방식(객체지향)대로 코드를 작성할 수 있게 되었다.

 

(이 부분을 쉽게 이해하고자 정리하자면, 객체 지향은 데이터를 재활용하기 위한 다양한 방법을 개발자에게 제공을 한다.

하지만 관계형 데이터베이스 시스템에서는 여러 쿼리 문을 사용해 마치 객체 지향 모델을 흉내내야 한다는 것이다.)

 

예를들어, 아래와 같은 간단한 상속관계가 있다고 해보자

 

위의 상속 관계를 표현하며 Album 객체를 RDB 에 저장하기 위해서는 Item 테이블과 Album 테이블을 모두 생성해야 한다.

 

우선 여기서 알 수 있는 한가지 사실은 Album 객체에 데이터를 저장하기 위해서는 총 2 개의 Query 문(Item, Album) 이 나가야 한다는 것이다.

 

또한 테이블에 저장한 Album 객체를 온전히 가져오기 위해서는 Item 테이블에 Album 을 Join 시킨 상태로 가져와야하고, 이를 위해서 Item 테이블에 Dtype 식별자 column(어느 테이블의 부모 아이템인가) 을 별도로 마련해야한다.

 

Item 조회 -> Dtype 을 통한 자식 테이블 찾기-> 자식 테이블에서 Item_id 끼리 일치하는 record join 의 과정을 거친다.

 

영속성 컨텍스트(1차 캐시)를 사용한다

JPA 기술의 핵심 중 하나인 영속성 컨텍스트는 메모리 상에 존재하는 1차 캐시의 역할을 수행한다.

 

왜 이런 영속화의 개념이 필요한지는 다음의 경우를 살펴보면 알 수 있다.

 

1. 데이터베이스로부터 같은 id 값을 가지는 Data 를 연속해서 두 번 조회하고 싶다.

Data fd = find("select * from DataTable");
Data fd2 = find("select * from DataTable");

어찌저찌 해서 데이터 베이스에 쿼리를 날려서 Data 객체를 반환하는 메소드를 작성했다고 쳤을 때, 단순히 데이터를 가져오는 로직만으로는 fd 와 fd2 의 동일성이 보장되지 않는다 (서로 다른 객체는 동등하다고 해서 동일함을 보장할 수 없다)

 

또한 분명 fd 와 fd2 는 같은 값을 가질 것으로 예상이 되는데(코드 사이에 아무런 SQL 문이 나가지 않았다), 아무런 로직 없이는 단건 쿼리가 두 번 나가는 비효율적인 상황이 생긴다

 

여기서 Java 의 Collection 을 생각해보면, 동등한 객체에 대한 동일성을 보장하는 방식을 사용하는 것을 알 수 있다.

동일함을 클래스의  hashCode() 값을 통해 판단하는데, 객체의 식별자 역할을 한다고 볼 수 있다.

 

그렇다면 어떻게 해야 위의 SQL 문을 통해 불러온 두 객체의 동일성을 보장(동일한 객체를 얻는 것을)할 수 있을까?

또 어떻게 동등한 객체를 요구하는 것으로 판단되는 맥락에서 여러번 조회 쿼리가 안나가도록 할 수 있을까?

 

여기서 사용되는 기술이 영속성 컨텍스트 와 Transaction 이다.

 

JPA 를 통한 성능 최적화가 가능하다

 

영속성 컨텍스트를 1차 캐시로 부르는 이유는 모든 데이터베이스 테이블을 참조하기 전에 이 영속성 컨텍스트를 항상 거쳐가기 때문이다.

 

영속성 컨텍스트에서는 영속화(persist)된 객체(엔티티)가 저장되고, 한번 영속화된 객체는 다시 데이터베이스로 Query 를 날릴 필요없이, 메모리에서 가져올 수 있다.

 

영속성 컨텍스트 내에서는 PK 값을 통해 동일한 객체임을 판단하며, 영속화가 되는 시점은 크게 엔티티를 조회할 때와 저장하려할 때로 나눌 수 있다. (em.persist(), em.find() 호출 시)

 

이러한 영속화가 지속되는 기간은 하나의 Transaction 이 끝날 때 까지(혹은 강제로 영속성 컨텍스트를 조작하는 메소드를 호출 할 때) 인데, Transaction 이 끝날 때 쌓여있던 쿼리가 데이터베이스로 날아가고, Transaction 을 관리하고 connection 을 가져온 EntityManager 는 폐기가 된다.

 

Transaction 은 데이터베이스에 접근하는 하나의 문맥으로 생각하면 좋다.

 

하나의 Transaction 내의 모든 Query 는 오류가 없어야 하고, 데이터의 무결성을 보장해야하는데, 이를 위해서 Transaction 은 쓰기 지연 방식을 사용한다.

 

쓰기 지연 방식은 하나의 Transaction 내에서 각 쿼리문을 곧바로 보내지 않고, Transaction 이 끝나는 지점에서 commit() 이 호출된 후에 쿼리를 한꺼번에 보내는데, 중간에 오류가 발생하면 rollback() 을 수행함으로써 데이터의 무결성을 보장한다.

 

또한 JPA는 지연 로딩(Lazy Loading) 을 지원하는데 연관관계에 있는 엔티티를 로드 시점에 모두 불러오는 것이 아니라, 엔티티의 필드를 직접 참조하려고 할 때 쿼리를 날리는 방식을 채택함으로써 쿼리 전송을 최소화 한다.

 

 

 

잘못된 정보가 있다면 댓글로 알려주시면 감사하겠습니다!!

'spring > jpa' 카테고리의 다른 글

[JPA] Join 과 Fetch Join (N+1 문제)  (0) 2022.08.04
Comments