먼 옛날 과거에는 객체를 DB에 저장하기 위해서 JDBC API와 SQL을 하나하나 작성해야했다. 대안책으로 JDBC 템플릿이나 Mybatis같은 SQLmapper가 등장했지만, 여전히 개발자가 SQL을 일일이 작성해야하는 문제는 해결되지 않았다. 그런데 JPA를 사용하면 SQL을 작성할 필요가 전혀 없다. 마치 자바 컬랙션에 객체를 저장하고 조회하는 것마냥 사용하면 된다.
JPA의 등장으로 개발과 유지보수 속도가 어마무시하게 높아지게 되었는데 JPA의 등장 배경, 특징을 알아가보자.
대부분의 객체지향언어를 사용한 개발을 진행한다고 했을 때 가장 많이 사용하는 데이터베이스는 관계형 DB이다.
즉, 객체를 관계형 DB에 저장하는 시대인 것이다!
그런데 개발을 하다보면 데이터베이스를 접근 및 수정해야하는 순간들이 경험하게된다. java 언어를 알아듣지 못하니 관계형 DB가 알아들을 수 있는 sql을 써야하는 사태가 발생한 것인데, sql을 쓰면 기본적인 쿼리를 날릴 때도 테이블마다 무한 반복되고 비효율적인 CRUD sql을 써야한다.
이처럼 SQL에 의존적이게 되면서 개발자는 개발에 집중하기 어려워진다. 개발하기에도 바빠죽겠는데 SQL까지 신경써야하기 때문이다.
그런데 애초에 관계형 데이터베이스가 나온 사상과 객체 지향 사상은 다르다.
관계형 DB는 데이터를 정교화해서 잘 보관이 목표인 반면,
객체지향은 필드 메서드가 잘 캡슐화되어 사용되는 것을 지향하기 때문이다.
앞에서 말한 것과 같이 object를 rds, nosql,file 등 RDB로 객체를 저장할 경우, 매핑시키는 sql를 작성하면서 개발자는 sql mapper가 되고만다...(객체 지향 개발과는 상관없는 일) 사실, 애초에 완전 다른 사상인 객체지향의 사상과 RDB사상을 정확하게 매핑 시키려고 하는 시도 자체가 이상하다고 봐야한다.
Object : O
RDB : X(좀 다른 개념)
객체의 상속과 그나마 유사한 개념은 Table 슈퍼타입과 서브타입이다. 예를들어 item이라는 부모 객체와 이를 상속받는 album, movie, book이라는 자식 객체들이 있다고 하자.
객체지향에서 album을 저장한다면 어떻게 될까?
album자체가 item의 특성을 상속받기 때문에 상속받는 album 객체를 한 번만 생성하면 된다. 조회 역시 album 객체를 한 번만 조회하면 된다.
그러나 DB에서 album은 새로운 객체 생성시 item, album 엔티티에 대한 insert 쿼리를 두 번 날려야한다. 또한 album을 조회할 때에는 item, album을 join해서 join table을 만들어 반환해야 한다... ( 조회시 매번 이 작업을 해야한다. 객체처럼 한꺼번에 넣는 구조, 빼오기로 한다면 얼마나 편할까...)
Object : O (레퍼런스를 가지고 .get객체명() 형태의 함수로 참조한다.)
RDB : X (Pk, fk로 join한다.)
객체지향에서는
참조를 사용해서 정보를 가져온다. 그러나 객체의 참조 방향이 양방향을 가질 수 없고 단방향 밖에 안된다.
ex) member.getTeam() - 가능
ex) team.getMember() - 불가능
RDB는
테이블의 외래 키를 사용하여 정보를 가져온다. 멤버에서 팀으로 가고 싶으면 조인해서 불러올 수 밖에 없다(번거로움). 그런데 단뱡향 참조만 가능한 객체지향과는 달리 RDB에서 테이블은 양방향 가능하다. 즉, member로 team 정보를 얻을 수 있고, 그 반대의 경우도 가능하다.
===
보통 객체를 테이블에 맞추어서 모델링한다.
여기서 객체에 참조키에 대한 컬럼을 작성할 때 id가 아닌 객체 그 자체를 필드로 선언한다. Insert할 때 무난하게 할 수 있으나 조회할 때는 조금 복잡해진다...조회시에는 처음 실행하는 SQL에 따라 조회범위가 달라지기 때문이다.(조회범위에 대해서는 지연로딩과 즉시로딩 개념을 보면된다.)
하다보면 복잡하다...
위와 같은 모델링은 계층형 아키텍처의 진정한 의미에서 계층 분할이 어렵다.
객체답게 DB를 모델링 할수록 매핑 작업만 늘어나기 때문이다. 객체를 자바 컬렉션에 저장 하듯이 DB에 저장할 수는 없을까라는 고민을 하고jpa가 나오게 된 이유도 이 때문이다.
그래서 이 완벽히 다른 두 패러다임 사이에 JPA가 등장한 것이다. 그런데 JPA를 알기 전에 먼저 ORM에 대해서 알아야 한다.
ORM이란 Obejct-relational mapping(객체 관계 매핑)으로서, 객체는 객체대로 RDB는 RDB대로 설계하도록 도와주는 프레임워크이다. 객체와 RDB사이에서 ORM 프레임워크가 중간에서 매핑하는 역할을 한다. 대중적인 언어에는 대부분 ORM이 존재한다.
ORM프레임워크로, 데이터베이스와 객체지향개발을 연결해주고 두 분야의 개발이 독립적으로 이루어질 수 있도록 한다.
JPA는 애플리케이션과 JDBC 사이에서 동작한다. java 애플리케이션과 jdbc API 사이에 JPA 있는데, 개발자가 직접 Jdbc api를 쓰는 것이 아니라Jpa에게 명령하고 그 Jpa가 jdbc api를 사용하여 sql 실행하는 것이다.
ex1) MemberDAO에서 객체 저장, 조회
1. JPA로 그냥 멤버 객체를 넘겨준다.
2. JPA가 멤버 객체를 분석하고 적절한 insert/select쿼리를 생성한다
3. JDBC API를 사용해서 DB에 전달하고 결과를 받는다.
4. (조회의 경우) 객체에 매핑
여기서 핵심은 개발자가 직접 쿼리문을 짜지 않아도 된다는 것과 데이터베이스와 객체지향의 패러다임 불일치를 해소해준다는 것이다.
JPA를 구현한 애들 중 많은 애들이 있는데 그 중 대표적인 애가 hibernate인 것이다!
JPA를 사용해야하는 이유는 크게 7가지이다.
SQL 중심 개발 -> 객체 중심 개발로의 전환
myBatis와 같이 SQL을 직접 작성하여 직접 DB를 조작하던 Mapper를 벗어나 개발에 집중할 수 있다.
생산성 향상
일일이 SQL을 써야했던 Mapper와는 다르게 그냥 불러서 쓰면 알아서 DB에서 조작/반영이 된다.
저장 : jpa.persist(member)
조회 : Member member = jpa.find(memberId)
수정 : memeber.setName("변경 이름")
삭제 : jpa.remove(member)
여기서 주의 깊게 봐야하는 것은 수정이다. 수정시 내가 원하는 값을 주면 DB에 update쿼리가 알아서 만들어진다. jpa를 쓴다는 것은 자바 collection 처럼 자유로이 넣다 뺐다 할 수 있다는 것이다. 개발자가 모든 부분을 구현하지 않아도 된다!
유지보수
DB에 새로운 컬럼이 추가되었다고 하자. 쿼리를 손댈게 없이 그냥 객체 class에 필드만 추가 하면된다! (SQL Mapper를 사용했다면 일일이 다 sql에도 추가했어야 했다)
패러다임의 불일치 해결
성능 최적화
transaction.begin(); // 트랜잭션 시작
em.persist(memeberA);
em.persist(memeberB);
em.persist(memeberC);
// 쓰기 지연이 없다면 세 과정을 우해 네똑을 세번 왔다갔다 해야한다.
// 쓰기 지연 -> 아직 inser SQL 데이터베이스에 보내지 않음
transaction.commit(); // 트랜잭션 커밋 - 커밋하는 순간 데이터베이스에 insert SQL을 모아서 보낸다.
// 지연로딩
Memeber member = memeberDAO.find(memberId); -> SELECT * FROM Memeber;
Team team = member.getTeam();
String teamName = team.getName(); -> SELECT * FROM Team;
// 즉시로딩
Memeber member = memeberDAO.find(memberId);
Team team = member.getTeam();
String teamName = team.getName(); -> select * from team;
// memeberDAO.find(memberId) 에서 이미 쿼리 join 쿼리가 생성됨
// -> select * M.*, T.* FROM Member JOIN Team
실제로 애플리케이션 개발시에 주로 쿼리를 많이 쓰는 지연로딩으로 쭈욱짜고 최적화가 필요한 부분에 즉시로딩을 적용할 수 있다.
데이터 접근 추상화와 벤더 독립성
표준
그러나 JPA는 사실 실무에서 사용하기 어렵다고 한다.
그 이유는 크게 다음과 같다.
객체와 테이블을 올바르게 설계하고 매핑하는 방법을 몰라서!
-> 실무에서는 한 두개가 아니라 수십개의 테이블이 복잡하게 얽혀 돌아간다. 자칫 한 두개 테이블을 작성하던 것에서 잘못 이해하고 적용하려고 한다면 폭망하게 된다.
JPA 내부 동작 방식을 몰라서!
내부 동작 방식을 잘 모르고 그냥 적용한다면, 오류가 발생했을 때 디버깅이 어렵고 그 지점을 찾기 어려울 수 있다. JPA가 내부적으로 어떻게 동작하고, jpa가 어떤 SQL을 만드는지, 언제 실행하는지 아는 것이 중요하다.
아직까지는 mybatis를 많이 사용하지만, JPA의 사용량과 검색 빈도수가 급등하고 있다.가까운 미래에 mybatis가 보다 JPA의 사용이 높아지지 않을까!
JPA는 객체지향과 RDB의 완전히 다른 두 패러다임 사이의 불일치에 의해 탄생한 ORM 이다. ORM은 객체와 RDB 두 기둥위에 있는 기술로, 두 패러다임 사이의 균형을 맞추고 sql을 직접 작성하는 번거로움을 덜어준다.
그러나 두 가지 패러다임이 완전히 대칭을 이룰 수 없기 때문에 모든 쿼리를 JPA로 해결하지 못하는 순간이 발생하기도 한다. 이럴 때는 직접 쿼리(특히 복잡한 쿼리)문을 작성해야한다.
결국엔 개발자는 JPA만을 사용할 수는 없으며 때에 따라 sql도 직접 작성해야한다. 따라서 JPA를 잘 사용하기 위해서는 객체, RDB 양쪽의 특징과 동작하는 법을 잘 이해하고 적절히 jpa와 sql을 사용할 수 있어야 한다.
[inflearn 김영한 님의] 자바 ORM 표준 JPA 프로그래밍 기본편 "JPA 소개"