ORM(Object Relational Mapping)
ORM은 객체와 관계형 데이터베이스 간의 차이를 중간에서 해결해 주는 하나의 프레임워크로 객체와 관계형 데이터베이스의 데이터를 매핑해 주는 기술이다.
그렇다면 ORM을 사용하는 이유가 뭘까?
패러다임 불일치 문제
객체지향 언어가 가진 장점을 활용하여 도메인 모델을 정의할 수 있지만 DB에 저장할 때 문제가 발생한다. 반면에 관계형 데이터베이스에서는 데이터 중심으로 구조화되어 있기 때문에 객체지향이라는 개념이 없어서 객체라는 것을 저장할 수가 없다.
객체가 단순하다면 get()을 통해서 모든 속성 값을 꺼내 DB에 저장할 수 있지만, 부모 객체를 상속받았거나, 다른 객체를 참조하고 있다면 객체의 상태를 저장하기는 쉽지 않다.
연관관계 문제
객체는 참조를 사용해서 다른 객체와 연관관계를 가지고 참조에 접근해서 연관된 객체를 조회한다.
하지만 관계형 데이터베이스의 테이블은 외래 키를 사용해서 다른 테이블과 연관관계를 가지고 조인을 사용해서 연관된 테이블을 조회하게 된다.
그리고 또 하나의 문제점으로는 객체는 참조가 있는 방향으로만 조회할 수 있지만 테이블은 외래 키 하나로 양방향 조회가 가능해진다.
결국 객체지향 개념과 관계형 데이터베이스 개념이 서로 잘 안 맞기 때문에 중간에서 연결해줄 수 있는 무언가가 필요하다.
중간에서 연결해줄 수 있는 그 무언가가 이번에 공부해 본 ORM 기술이다.
참고로 ORM 기술은 자바 외에 다른 객체지향 언어들도 다 가지고 있는 기술이다.
JPA(Java Persistence API)
JPA는 자바 진영에서 사용하는 ORM 기술 표준으로 자바 애플리케이션과 JDBC 사이에서 동작하게 된다.
또한, JPA는 자바 어플리케이션에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스로 여러 가지 구현체가 존재하지만 그중 Hibernate가 가장 많이 사용된다.
JPA의 장단점
장점
- 지루하고 반복적인 코드와 CRUD용 SQL을 개발자가 직접 작성하지 않아도 JPA가 대신 처리해주기 때문에 생산성이 올라간다.
- 패러다임의 불일치 문제를 해결하여 객체지향 설계가 가능해져서 유연한 설계가 가능하고 그로 인한 유지보수가 향상된다.
단점
- 복잡하고 무거운 쿼리는 속도를 위해 별도의 튜닝이 필요하게 되는데 이로 인해서 결국에는 쿼리문을 직접 사용해야 될 수도 있다.
- 프로젝트의 규모가 크고 복잡하여 설계가 잘못된 경우 속도 저하 및 데이터 일관성을 무너뜨릴 수도 있다.
EntityManager
JPA에서 가장 중요한 개념 중 하나로 EntityManagerFactory에서 생성된다.
JPA가 제공하는 기능은 크게 엔티티와 테이블을 매핑하는 설계 부분과 매핑한 엔티티를 실제 사용하는 부분으로 나눌 수 있는데 엔티티 매니저는 엔티티를 저장하고, 수정하고, 삭제하고, 조회하는 등 엔티티와 관련된 모든 일을 처리한다.
엔티티 매니저를 사용하면서 한 가지 주의해야 될 점이 하나 있는데 EntityManagerFactory는 만드는 비용이 크기 때문에 한 개만 만들어서 여러 스레드들이 사용할 수 있게 해 주지만, 반대로 엔티티 매니저는 여러 스레드가 동시에 접근하면 동시성 문제가 발생하므로 스레드 간에 공유는 절대 하면 안 된다.
영속성 컨텍스트(Persistence Context)
JPA에서 가장 중요한 논리적인 개념으로 JPA를 이해하는 데 가장 중요하다.
영속성 컨텍스트는 엔티티 매니저를 생성할 때 하나 만들어지고 엔티티 매니저로 엔티티를 저장하거나 조회하면 엔티티 매니저가 영속성 컨텍스트에 엔티티를 보관하고 관리하게 된다.
참고로 여러 엔티티 매니저가 같은 영속성 컨텍스트에 접근할 수도 있다.
엔티티의 생명주기
엔티티의 생명주기에는 총 4개의 과정이 있는데 코드와 그림을 통해 하나씩 살펴보자.
비영속
말 그대로 영속성 컨텍스트나 데이터베이스와는 전혀 관계가 없는 상태로 객체 생성 코드를 통해서 순수한 엔티티 객체 상태이다.
영속
엔티티 매니저를 통해서 엔티티 객체를 영속성 컨텍스트에 저장한 상태로 위의 그림과 같이 영속성 컨텍스트가 관리하는 엔티티를 영속 상태라고 한다.
준영속
영속성 컨텍스트가 관리하던 영속 상태의 엔티티가 영속성 컨텍스트로부터 분리된 상태이다.
삭제
엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제한다.
영속성 컨텍스트의 특징
영속성 컨텍스트는 엔티티를 식별자 값(@Id)으로 구분한다. 따라서 영속 상태는 식별자 값이 반드시 있어야 한다. 식별자 값이 없으면 예외가 발생한다.
JPA는 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영하는데 이것을 플러시라고 한다.
1차 캐시
영속성 컨텍스트 내부에 있는 캐시로 영속 상태의 엔티티는 모두 이곳에 저장된다.
트랜잭션을 커밋하거나 플러시를 호출하면 1차 캐시에 있는 엔티티의 변경 내역을 데이터베이스에 동기화한다.
쿼리 저장소(쓰기 지연 SQL 저장소)
엔티티 매니저가 트랜잭션을 커밋하기 직전까지 내부 쿼리 저장소에 모아둔다.
커밋할 때 모아둔 쿼리를 데이터베이스에 보낸다.
2차 캐시
애플리케이션에서 공유하는 캐시를 2차 캐시라고 하는데 해당 캐시는 애플리케이션이 종료될 때까지 유지된다.
2차 캐시를 적용하면 엔티티 매니저를 통해 데이터를 조회할 때 우선 2차 캐시에서 찾고 없으면 데이터베이스에서 찾는다.
2차 캐시를 적절히 활용하면 데이터베이스 조회 횟수를 획기적으로 줄일 수 있다.
2차 캐시는 동시성을 극대화하려고 캐시한 객체를 직접 반환하지 않고 복사본을 만들어서 반환하게 된다. 만약 캐시한 객체를 그대로 1차 캐시에게 반환하게 된다면 여러 곳에서 같은 객체를 동시에 수정하는 문제가 발생할 수 있다.
동시에 수정하는 것을 막기 위해서 객체에 락을 거는 방법도 있지만 그렇게 하면 동시성이 떨어질 수 있다. 락도 안 걸고 동시성도 극대화시키기 위해서 락에 비해 상대적으로 비용이 저렴한 객체를 복사하는 방법을 사용하게 된다.
'JPA' 카테고리의 다른 글
JPA - 엔티티(Entity)와 CRUD (2) | 2024.01.09 |
---|