[강의 정리] JPA 소개

나무·2023년 11월 27일

JPA 

목록 보기
1/11
post-thumbnail

1. JPA 이전의 역사

자바 개발자라면 무조건 알아야하는 기술 스택 중 하나며 자바 개발자를 준비하는 사람이라면 한번쯤은 꼭 들어봤을것이다. 그렇다면 도대체 JPA가 뭘까?

Java Persistence API 의 약자로 자바 진영에서 ORM(Object-Relational Mapping) 기술 표준으로 사용되는 인터페이스의 모음이다.

흠,,, 한번에 와닿는 설명이 아니다. 그리고 일단 ORM이 뭔지도 모르겠다. 우선 이것들을 설명하기 전에 먼저 JPA 이전의 역사에 대해 조금은 알 필요가 있다. 그래서 그것들부터 한번 짚고 넘어가보도록 하자.

JDBC (Java Database Connectivity)

모든 서버들은 자신이 실행하는 프로그램이 사용할 데이터를 저장하고 불러오는 DB가 따로 존재한다. 문제는 서버랑 DB는 서로 체계가 다르기 때문에 서버가 DB에 접속하기 위해서는 서로를 연결해줄 무언가가 필요로하다.

이때 사용되는것이 바로 JDBC 이다.

JDBC는 자바 프로그램이 데이터베이스에 접속할 수 있도록 해주는 자바 API 기술이다.

이 기술 덕분에 모든 자바 프로그램들은 DB에 접속하여 데이터를 집어넣고 꺼낼수 있게 되었다.
하지만 기쁨도 잠시, 순수 JDBC를 사용할 경우에는 개발자들이 손수 모든 SQL 쿼리문을 작성해줘야했다. 뿐만아니라 쿼리 하나를 보내는데 필요한 수많은 전후처리 코드들이 덕지 덕지 붙어있어서 굉장히 지저분해졌다.

또한 DB로부터 가져온 데이터들을 자바 객체의 각 필드마다 값을 집어넣어주는 SQL 매핑 역할을 개발자가 손수 해줬어야했다.

SQL매퍼의 등장 (Spring JDBC, MyBatis)

SQL매퍼 란 Object와 SQL의 필드을 매핑하여 데이터를 객체화하는 기술이다
[출처]

개발자들은 하나의 쿼리를 보내는데 수십줄의 중복 코드를 짜는데 매우 지쳐있었고 이런 개발자들의 고통을 덜어준것이 바로 SQL 매퍼 이다.

SQL 매퍼 덕분에 개발자들은 이제 더이상 DB에서 온 데이터를 직접 자바객체에 필드마다 값을 세팅해줄 필요가 없어졌으며 또한 JDBC 에서 가장 큰 문제였던 동적쿼리 를 해결해주었다.

Spring JDBC

Spring JDBC 도 어느정도 간단한 SQL매핑을 지원해준다.

MyBatis


MyBatis 의 가장 강력한 기능중 하나가 바로 "동적 쿼리" 생성이다.

2. SQL 중심 개발의 한계점

SQL 매퍼덕분에 굉장히 많은 문제들이 해소된듯 보였다. 아니 정말 많이 해소가 되었다. 하지만 인간의 욕심은 끝이없었으니,,, 점차 사용하다보니 불편한점들이 서서히 눈에 띄기 시작했다.

우선 기본적으로 사용하는 DBMS가 달라질경우 작성했던 SQL 들을 전부 수정해줘야했다.
하지만 RDBMS와 객체지향 언어간에는 이 보다 더 근본적인 문제점이 존재했다.

패러다임의 불일치

패러다임의 불일치 란, 소프트웨어 개발에서 발생하는 문제 중 하나로 서로 다른 프로그래밍 패러다임 간에 발생하는 개념, 모델, 접근 방식 등의 불일치를 나타낸다.

상속의 불일치

객체지향 프로그래밍에서는 클래스 상속을 통해 코드를 재사용하고 추상화를 제공하는 반면 관계형 데이터베이스는 테이블 간의 상속을 직접적으로 지원하지 않는다. 그래서 객체의 상속관계를 RDBMS의 테이블로는 모델링 하기가 어려우며 사실상 불가능하다.

상속 관계를 똑같이 DB테이블에 모델링 시킬시, 데이터를 가져오고 집어넣는 쿼리가 굉장히 복잡해진다.

[컬렉션 사용]
만일 Album 이라는 자바의 객체를 DB가 아니라 그냥 컬렉션에 저장할 경우

List<Item> items = new ArrayList();
items.add(album);

이렇게 구현이 되지만 DB에 저장을 하려면

[DB 사용]
먼저 데이터를 객체에서 다 꺼낸다음 테이블별로 INSERT 문을 작성해주어야한다.

INSERT INTO ITEM VALUES(album.getId(), album.getName(), album.getPrice());
INSERT INTO ALBUM VALUES(album.getArtist());

지금은 상속계층이 2개이지만 만약 여러 레벨로 상속된 객체일 경우, 계층마다 INSERT 문이 증가하게 된다.


데이터 식별성의 불일치

객체는 주로 동일성(identity)을 객체의 메모리 주소 등을 통해 식별하는 반면 관계형 데이터베이스는 주로 기본 키(primary key)를 사용하여 데이터의 식별성을 관리한다. 이로 인해 두 패러다임 간의 식별성 처리에 어려움이 발생할 수 있다.

[컬렉션 사용]
만일 id==1 인 member를 DB에서 꺼내지 않고 컬렉션에서 꺼낼경우

Member member1 = items.get(1);
Member member2 = items.get(1);

member1 == member2

같은 곳을 참조하고 있으므로 서로 하나의 id로 같은 몇번이고 꺼내어도 같은 객체를 꺼낼 수 있다.


[DB 사용]
하지만 DB에서 id를 통해 Member 데이터를 꺼내올 경우에는.

SELECT id,name,age FROM MEMBER WHERE id = "A001";

DB는 PK(id) 를 통해 데이터를 구분하기 때문에 id 만 같으면 항상 같은 데이터임이 보장이된다. 하지만 자바에서는 다르다.

Member member1 = memberDao.findById(1);
Member member2 = memberDao.findById(1);

// member1 != member2 !!!

같은 id (1) 로 가져왔지만 객체에 저장하게 되면서 두 객체는 서로 다른 객체가 된다. 왜냐하면 findById() 가 실행 될 때 인스턴스를 새로 생성한다음 거기에 데이터를 담기 때문에 자바에서는 메모리 주소가 달라지므로 서로 다른 인스턴스가 된다.


객체와 테이블의 불일치

객체지향 프로그래밍에서는 개체들 간의 관계와 상속 등을 강조하는 반면에 관계형 데이터베이스는 주로 테이블과 열의 구조를 가지며, 데이터의 관계를 테이블 간의 연결로 표현한다. 이로 인해 객체를 테이블에 저장하고 조회할 때 매핑에 어려움이 발생 한다.

RDBMS를 제대로 사용하기위해서는 객체 모델링을 테이블에 맞게 해주어야한다.
테이블 : FK를 통해 서로 다른 테이블과 연관관계를 맺는다.
객체 : 필드멤버에 다른 객체를 참조함으로써 연관관계를 맺는다.

[컬렉션 사용]
객체의 경우 객체끼리 상호 연관관계를 맺고있다면 접근연산자를 이용해 자유롭게 순회를 할 수 있다. 그래서 언제든지 참조하고 있는 객체의 데이터를 조회할 수 있다.

Long id1 		= items(1).team.id;
String name1 	= items(1).team.name;
Long id2 		= items(2).team.id;
String name2 	= items(2).team.name;

하지만 DB에서는 필요할때마다 JOIN 을 하여 테이블들을 불러와야한다. 문제는 처음 실행되는 SQL에 따라 가져올 데이터 범위가 정해지는데 그 시점에는 사용자가 어떤 데이터를 불러오고 싶어하는지를 알 수 없다는 것이다.

[DB 사용]

/*Member만 가져옴 */
SELECT * FROM MEMBER;

/*Member와 Team 모두 가져옴 */
SELECT m.*, t.* 
FROM MEMBER m 
INNER JOIN TEAM t;

이 경우 2가지 해결책이 존재한다.

1) Member를 부를때마다 항상 모든 연관된 테이블을 다 불러오기
2) 각 경우 마다 분리해서 DAO의 인터페이스를 만들기.

하지만 1)의 경우 연관관계가 많을수록 로드 해야할 데이터가 너무 많아지며, 2)의 경우 연관관계가 많을 수록 생성해야할 메서드가 너무 많아진다.

이외에도 데이터 타입의 불일치 가 존재한다.

결론

결국 자바와 같은 객체지향 언어로 개발을 할 경우 매우 높은 확률로 RDBMS 를 사용하게되는데 문제는 두 프로그램의 패러다임이 서로 불일치하기 때문에 이것을 해결하기위해선 중간에서 복잡한 매핑 작업들이 수반 되어질 수 밖에 없다.

결국 SQL 매퍼 는 객체와 테이블간의 관계를 매핑하는 것이 아니라, SQL문을 직접 작성하고 쿼리 수행결과를 어떠한 객체에 매핑하여 줄 지 바인딩하기 때문에 SQL 의존적인 방법이다.

이를 해결하기위해 ORM 이 등장하게된다.

3. ORM의 등장

ORM 기술은 사실 과거에도 있었다. EJB라고 하는 악명높은 자바 프로그램 개발 프레임워크가 있었는데 현재의 스프링과 같은 포지션이였다. 하지만 EJB에서 지원하는 ORM은 너무나 사용법이 복잡했기 때문에 개발자들에게 기피되었다.

하이버네이트

그래서 개빈킹 이라는 개발자는 새로운 ORM을 개발해냈는데 그것이 바로 하이버네이트 다. 그럼 JPA는 뭐냐 하시는 분들이 계실건데 JPA는 ORM 기술에 대한 자바 표준 명세이고 하이버네이트는 이를 구현한 구현체이다.

하지만 웃긴점은 하이버네이트가 JPA보다 먼저 나왔다. 즉, 설계도가 제품보다 먼저나온셈인데 사실 표준이란게 늘 먼저 기술들이 발명되고 그 후에 표준이 정립되기 마련이다. JPA 또한 하이버네이트가 나오고 나서 여러 ORM 들이 만들어지니깐 애플리케이션간의 이식성을 높이기 위해 마련된 것이다.

ORM의 장점

그래서 ORM이 뭐가 좋은가? 일단 ORM의 정의부터 알아보자.

Object와 DB테이블을 매핑하여 데이터를 객체화하는 기술.
출처

언뜻 보면 SQL매퍼랑 차이가 없어보인다. 둘다 DB의 데이터를 객체와 매핑 시켜주는것인데 자세히보면 단어 하나가 다르다.

ORM 은 SQL매퍼와 달리 필드가 아닌 DB 테이블 과 객체를 매핑 시키는 기술이다. ORM은 필드 단위가 아닌 더 넓은 범위의 테이블을 매핑하기에 테이블과 객체간의 관계를 파악할 수 있고 각 필드들이 PK인지 FK인지도 확인할 수 있는 등 다양한 작업들이 가능해졌다.

즉 ORM은 나무가 아닌 숲을 바라보는 것이다.

이런 이유로 ORM은 이전의 문제들이였던 패러다임의 불일치들을 스스로 적절한 전략들을 이용해 해결해나가며, 또한 SQL을 추상화하여 이제 더 이상 개발자가 SQL을 직접 치지 않고도 DB로부터 데이터를 불러올 수 있도록 자동화 하였다.

장점을 좀 정리해보면,

객체와 테이블 간의 매핑
객체를 데이터베이스 테이블에 자동으로 매핑하여 객체 간의 관계를 유지한다.

CRUD 연산 지원
ORM은 객체에 대한 생성(Create), 읽기(Read), 갱신(Update), 삭제(Delete) 연산을 데이터베이스에 대한 SQL 쿼리를 작성하지 않고도 제공해준다.

객체 지향적인 쿼리 언어
ORM은 객체 지향적인 쿼리 언어를 제공하여, 객체에 대한 쿼리를 직관적으로 작성할 수 있다.

데이터베이스 독립성
ORM은 데이터베이스에 대한 구체적인 구현을 추상화하고, 다양한 데이터베이스 시스템에서 사용할 수 있다.

본 포스트는
김영한의 자바 ORM 표준 JPA프로그래밍 기본 강의 및 도서를 참고하여 정리했습니다.

profile
🍀 개발을 통해 지속 가능한 미래를 만드는데 기여하고 싶습니다 🍀

0개의 댓글