1. SQL 중심 개발의 문제점
지금 객체는 대부분이 관계형 데이터베이스에 저장되어 관리된다.
하지만, 데이터베이스는 SQL 만을 알아들을 수 있기 때문에 결국 객체를 데이터베이스에 저장하고, 조회하는 작업을 하기 위해선 객체와 데이터베이스 사이에 SQL 문으로 변환하는 반복적인 작업이 필요하다. 예를 들어 테이블에 필드 하나가 추가되었다고 하면 해당 테이블의 INSERT, SELECT 등의 쿼리문에 해당 필드를 일일이 추가해주어야 하는 번거로운 일이 발생한다.
결과적으로 애플리케이션은 객체 지향적인 설계를 하고 개발하기 원하지만 막상 SQL문을 작성하는데 시간을 매우 할애하게 되어 SQL 작성이 중심이 되는 개발이 된다는 것이다.
2. 객체 vs 관계형 데이터베이스 패러다임의 불일치
상속

Item 이 부모 클래스, Album, Movie, Book 이 자식 클래스라고 해보자.
Album을 저장할 때 실제 SQL에선 INSERT INTO ITEM, INSERT INTO ALBUM 두 번을 분해하여 날려야 한다.
조회할 때도 ITEM 테이블의 정보를 같이 가져와야 하므로 JOIN SQL 문을 작성해야 하는 등 매우 번거롭다.
연관 관계

Member 로부터 연관 관계에 있는 Team 을 조회해야 한다고 하자.
객체는 참조를 사용한다. 따라서 member.getTeam() 으로 Team 객체를 불러올 수 있다.
테이블은 외래키(FK)를 사용하여 JOIN ON M.TEAM_ID = T.TEAM_ID 와 같이 FK 와 PK를 통해 조인하여 불러와야 한다.
객체는 참조가 있는 방향으로만 참조가 가능하다.
따라서, 객체는 Member 에선 Team 을 접근할 수 있지만 Team 에선 Member 로 접근이 불가능하다.
그러나, 테이블은 PK, FK 로 조인하여 양방향이 가능하다! 이 차이도 알아두자 👍
따라서, 객체를 테이블에 맞추어 모델링한다면 필드에 외래키인 TEAM_ID 만을 가지고 있으면 되지만, 객체의 관점에선 즉, 객체다운 모델링을 위해선 참조를 하기 위해 필드에 외래키가 아닌 Team 객체를 가지고 있어야 한다. 이럴 경우 SQL 작성 시 외래키인 TEAM_ID 필드값을 같이 넣어줄 때 member.getTeam().getId() 로 참조를 타고가야 한다... 여기까진 그럴 수 있다고 치자!
가장 문제는 객체 모델링을 조회할 때이다.
SELECT M.*, T.*
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
public Member find(String memberId) {
//SQL 쿼리를 날려 데이터를 가져옴
Member member = new Member(); // 멤버 객체 생성
// 멤버 객체에 조회해온 데이터를 필드에 세팅
Team team = new Team(); // 팀 객체 생성
//팀 객체에 조회해온 데이터를 필드에 세팅
member.setTeam(team); //멤버와 팀의 연관 관계 세팅
return member;
}
멤버 객체를 조회하기 위해선 참조하는 팀 정보도 필요하므로 멤버와 팀을 조인하여 가져온 데이터를 각각 생성한 멤버 객체와 팀 객체에 세팅해주고 연관 관계까지 세팅해준 다음 멤버 객체를 넘겨주어야 한다. 자바 컬렉션 세상에선 리스트에 Member 객체를 넣어놓기만 하면 언제든 객체를 꺼내어 member.getTeam() 를 통해 참조만 하면 되는데!
=> 매우 번거롭다...😥
객체 그래프 탐색

객체의 입장에선 멤버가 주문한 아이템을 찾기 위해서 member.getOrder().getOrderItem() 와 같이 참조를 타고 들어가서 원하는 데이터를 찾을 수 있다. 따라서 그래프 탐색 범위의 제한이 없다.
그러나 SQL 은 처음 실행하는 SQL 문에 따라 그래프 탐색 범위가 제한된다.
SELECT M.*, T.*
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
만약 위와 같이 Member, Team 테이블을 조인하여 멤버와 팀의 데이터만 조회했다면 member.getTeam() 은 가능하지만 member.getOrder() 은 주문 데이터를 가져오지 않아 null일 것이다.
이렇게 작성한 SQL에 따라 탐색 범위가 달라진다면 계층형 아키텍처에서의 진정한 계층 분할이 어려워진다.
Service 단에서 엔티티가 어디까지 탐색이 가능한지도 모르는 데 자유롭게 엔티티에 접근할 수 있을까.
탐색 범위를 알려면 DAO단의 작성된 SQL을 결국 직접 확인해주어야 한다. 이것은 엔티티가 SQL 에 종속적이기 때문에 발생하는 문제이다.
객체 동등성 비교
Long memberId = 1L;
Member member1 = memberDAO.getMember(memberId);
Member member2 = memberDAO.getMember(memberId);
member1 == member2 // 다르다!!!
Member member1 = list.get(memberId);
Member member2 = list.get(memberId);
member == member2 // 같다!!
위 코드는 1번 아이디를 가진 멤버 데이터를 DAO 단을 거쳐 DB에서 조회해온 후 각각 반환 받은 객체를 비교한 것이다. 결과는 다르다고 나온다. 왜냐? DAO 에서 SQL 쿼리를 날려 데이터를 가져온 후 새로운 객체를 각각 생성하여 필드를 세팅한 후 반환하였기 때문이다. 즉, 각각 다른 객체가 생성된 것이다.
그러나 객체를 DB 가 아닌 자바 컬렉션 세상에서 리스트에 저장한다고 해보자. 리스트에 멤버 객체를 넣고 똑같이 1번 아이디로 조회하여 객체를 꺼내었을 때 객체를 비교하면 같다고 나온다. 리스트에선 같은 번호로 조회하면 실제로 같은 객체를 참조하기 때문이다.
객체와 데이터베이스는 애초에 지향점이 다르게 만들어 진 것이기 때문에 패러다임의 불일치 문제가 일어날 수 밖에 없다. 그리고 이 불일치 문제를 해결하기 위한 작업에 개발자가 너무 많은 시간을 쏟게 된다.
이러한 SQL 중심 개발의 문제와 객체와 데이터베이스 패러다임의 불일치 문제를 해결하기 위해 나온 것이 JPA 이다 🙌 JPA에 대한 자세한 소개는 다음 포스팅에서 다루겠다.
자료 출처 - 김영한의 < 자바 ORM 표준 JPA 프로그래밍 - 기본편>
'JPA' 카테고리의 다른 글
| [JPA] em.clear() 후 엔티티의 getId() 동작에 대한 의문점 🙋♂️ (0) | 2024.02.18 |
|---|---|
| [JPA] JPA 사용을 위한 설정과 동작 이해 (0) | 2024.02.01 |
| [JPA] JPA란 무엇인가? (1) | 2024.01.31 |