보통을 개발을 하면 객체를 만들고 그 객체를 관계형 DB에 보관하는 일이 많습니다. 이 과정에서 SQL 중심적인 개발이 생겨나고 개발자는 객체와 DB 테이블 사이의 SQL 매퍼와 같은 반복적인 CRUD 작업을 해야 했습니다. 심지어 객체와 관계형 DB 테이블 사이에 패러다임 불일치로 반복적인 SQL을 개발자가 직접 작성하여 그 불일치를 해소해야 했습니다.
예를 들어 Album 객체를 DB에 저장하기 위해서는 객체를 생성하고 INSERT 쿼리를 직접 작성해야 합니다. 만약 Album 객체가 Item 객체를 부모 클래스로 두고 있다면, Album 객체와 함께 Item 객체의 INSERT 쿼리도 작성해야 합니다. Album을 조회할 때는 더 복잡합니다. 물론 DB 테이블끼리 상관관계를 가지면 너무 복잡하기 때문에 잘 사용하지 않습니다.
하지만 우리가 DB 테이블에 자바 객체를 저장하지 않고, 단순히 자바 컬렉션에 저장할 때를 생각해 봅시다. 아래 코드와 같이 단순히 add 함수로 컬렉션에 객체를 저장하고 get 함수로 컬렉션의 객체를 조회합니다.
list.add(album);
Album album = list.get(albumId);
Item item = list.get(albumId);
이처럼 자바 컬렉션에 데이터를 저장하는 방식처럼 관계형 DB에 객체를 저장하기 위해 JPA가 등장했습니다. 만약 SQL을 직접 사용한다면 객체 설계 또한 DB 테이블에 맞춰서 모델링해야 합니다. 아래 Member 클래스와 같이 참조하는 객체의 주소 값을 가진 것이 아닌 id를 가져야 테이블에 조회하기 쉽기 때문입니다.
class Member {
String id;
Long teamId;
String username;
}
class Team {
Long id;
String name;
}
위와 같은 모델링으로 INSERT 쿼리를 아래와 같이 구현했다고 생각해 봅시다. 만약 Member 객체에 필드가 추가되면 어떻게 될까요? INSERT 쿼리까지 다 수정해야 합니다. 이 과정에서 실수를 한다면 당연히 버그로 이어지게 됩니다.
INSERT INTO MEMBER(MEMBER_ID, TEAM_ID, USERNAME) VALUES ...
또한 Member 클래스처럼 참조 대상의 id 값을 가진 설계는 객체다운 모델링이 아닙니다. 테이블의 경우 외래 키를 사용해 연관관계를 나타내기 때문에 위와 같이 id 값으로 참조를 표현하는 게 적절하지만, 객체는 참조를 통해 연관관계를 나타내기 때문입니다.
객체다운 모델링을 위해서는 teadId가 아닌 Team 객체를 참조로 가져야 합니다. 하지만 객체다운 모델링은 DB에 쿼리를 짜기 더 어렵게 만듭니다. id 값뿐이라면 id 값으로 DB 테이블에 접근하면 되겠지만, 객체라면 해당 객체의 id 필드를 getter로 받아와서 DB 테이블에 접근해야 하기 때문입니다.
이때 JPA를 사용하면 객체를 객체답게 모델링하고 DB 테이블에 저장하는 과정도 컬렉션에 저장하듯이 쉽게 작업할 수 있습니다.
마지막으로 객체의 경우 참조 관계와 상속 관계 그래프를 자유롭게 탐색할 수 있습니다.
하지만 SQL을 사용해 DB 테이블을 탐색할 경우에는 처음 실행하는 SQL에 따라 탐색 범위가 결정됩니다. 아래와 같이 Team 데이터를 조회하는 SQL을 작성했다고 가정해 봅시다.
SELECT M.*, T.*
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
member.getTeam(); // ok
member.getOrder(); // null
SQL로 Team 데이터를 조회했습니다. 위의 객체 그래프를 보면 Team과 Order는 참조 관계를 가집니다. 하지만 member.getTeam()은 가능하지만, member.getOrder()는 null이 됩니다. SQL에서는 처음 실행하는 SQL에 따라 탐색 범위가 결정되면서 엔티티를 신뢰할 수 없게 됩니다.
class MemberService {
...
public void process() {
Member member = memberDAO.find(memberId);
member.getTeam(); //???
member.getOrder().getDelivery(); // ???
}
}
memberDAO.find(memberId)를 통해 SQL로 DB 데이터를 탐색했더라도, member.getTeam으로 Team 데이터까지 조회할 수 있는지, member.getOrder().getDelivery()가 null이 안 나올지 알 수 없습니다. 결국에 find 함수에 들어가서 어떤 SQL 문이 작성되어 있는지 탐색 범위가 어떻게 되는지 확인해야 합니다. 그렇다고 SQL을 통해 사용하지 않는 모든 객체를 미리 로딩할 수도 없습니다. 성능도 크게 저하되고 작성해야 할 쿼리도 많아지기 때문입니다. 따라서 SQL을 사용하면 아래와 같이 조회할 수 있는 범위에 따라 함수를 구분하는 방식을 사용합니다.
memberDAO.getMember(); //Member만 조회
memberDAO.getMemberWithTeam();//Member와 Team 조회
//Member,Order,Delivery
memberDAO.getMemberWithOrderWithDelivery();
하지만 절대 좋은 해결책이라고 볼 수 없습니다. 계층형 아키텍처를 구현하고자 했지만 물리적으로 계층을 분할할 뿐 논리적으로 계층을 분할하지 못하게 됩니다.
결국 SQL을 사용하면 객체답게 모델링할수록 매핑 작업만 늘어나고 객체를 자바 컬렉션에 저장하듯이 DB에 저장하기 위해 JPA가 등장한 것입니다.
'Spring > JPA' 카테고리의 다른 글
일대일 연관관계 매핑 (1) | 2024.10.17 |
---|---|
연관관계 매핑 기초 (1) | 2024.10.17 |
기본키 매핑 (0) | 2024.10.17 |
영속성 컨텍스트 플러시 (1) | 2024.10.16 |
영속성 컨텍스트 (2) | 2024.10.16 |