JPA) 사용해야하는 이유 (SQL 의존적 개발의 문제)
Date: Updated:카테고리: java
SQL에 의존적인 개발
- 주로 어플리케이션을 개발할 때 관계형 데이터베이스(RDB)를 사용한다. (Oracle, MySQL, PostgreSQL,..)
-
객체를 영구 보관하는 다양한 저장방안이 존재하지만 현실적인 대안은 관계형 데이터베이스(RDB)다.
- 반복적이며 지루한 SQL 코드를 매번 작성해야 한다.
INSERT INTO
…UPDATE
…SELECT
…DELETE
…- 자바 객체를 SQL로 변환 작업 (혹은 SQL 결과를 자바 객체로 변환)
- 유지보수에 용이하지 않다
- EX) 예를 들어 SQL에 필드를 하나 추가한다고 했을 때..
- SQL에 의존적인 개발을 피하기 어렵다!
패러다임의 불일치 발생
객체지향의 사상과 RDB의 사상이 다르다!
- RDB
- 객체를 잘 정규화 하여 저장소에 보관하는 것이 목표
- 객체
- 필드 및 메서드를 묶어서 잘 캡슐화 하는것이 목표
-
‘객체 지향 프로그래밍은 추상화, 캡슐화, 정보은닉, 상속, 다형성 등 시스템의 복잡성을 제어할 수 있는 다양한 장치들을 제공한다.’ - 어느 객체지향 개발자
사상이 다른 두 가지를 연결(매핑)지으려다 보니 여러 문제가 발생한다.
- RDB는 SQL밖에 인식하지 못하므로 결국 객체를 SQL 형태로 구현해야함.
- 결국 RDB와 관련된 기능을 구현할 때 마다 매번 SQL을 구현해야하는 처지에 놓임
- 나는 개발자인가 SQL 작성자인가? (개발자 ≓ SQL 매퍼)
객체와 관계형 데이터베이스의 차이
상속
- 객체
- 상속관계가 존재한다.
- RDB
- 상속관계가 존재하지 않지만 유사한 방식의 슈퍼타입-서브타입 물리 모델이 있다.
- Album 객체를 RDB에 저장할 때
- 객체를 분해한다.
- Album이 Item을 상속받고 있으므로
INSERT
쿼리를 2번 날린다.INSERT INTO ITEM ...
INSERT INTO ALBUM ...
- 객체를 분해한다.
- Album 객체를 RDB에서 조회할 때
- Album 과 Item 테이블을
JOIN
해서 SQL 결과로 가져온다. - Album 과 Item 객체를 생성하고 각 필드에 set 한다
- Album이 아닌 Movie, Book 등을 조회하려면 위 과정을 반복해야한다..
- 너무 복잡하며 불편하다. 그래서 DB에 저장할 객체에는 상속관계를 사용하지 않는다!
- Album 과 Item 테이블을
- 만약 DB가 아니고 자바 컬렉션에서 관리를 한다면? (해결 가능성)
- 저장
list.add(album)
INSERT
쿼리를 2번 날릴 필요 없이 너무 간단하다!
- 조회
Album album = list.get(albumId);
- 부모 타입으로 조회 (다형성 활용)
Item item = list.get(albumId);
JOIN
쿼리 작성, 각각의 객체 생성 등 복잡한 과정을 거칠 필요가 없다!
- 저장
연관관계
- 객체
- 참조를 사용한다.
member.getTeam();
- 단방향으로만 관계가 존재한다.
- Member의 경우 Team 타입의 속성이 존재하지만 Team은 Member 타입의 속성이 없다.
- 참조를 사용한다.
- RDB
- 외래키를 사용한다.
JOIN ON M.TEAM_ID = T.TEAM_ID
- 양방향으로 관계가 존재한다.
- MEMBER 테이블의 TEAM_ID 외래키를 통해서 가능하다!
MEMBER.TEAM_ID = TEAM.TEAM_ID
(MEMBER 테이블 기준 조회)TEAM.TEAM_ID = MEMBER.TEAM_ID
(TEAM 테이블 기준 조회)
- MEMBER 테이블의 TEAM_ID 외래키를 통해서 가능하다!
- 외래키를 사용한다.
객체의 모델링 과정 중 문제
객체를 테이블에 맞춰서 모델링 한 뒤 테이블에 저장
class Member {
String id; //MEMBER_ID 컬럼 사용
Long teamId; //TEAM_ID FK 컬럼 사용
String username;//USERNAME 컬럼 사용
}
class Team {
Long id; //TEAM_ID PK 사용
String name; //NAME 컬럼 사용
}
// insert SQL
/* INSERT INTO MEMBER(MEMBER_ID, TEAM_ID, USERNAME) VALUES ... */
위 방법은 객체지향 관점에서 봤을때 객체지향스럽지 않다. (객체다운 모델로 수정하자.)
class Member {
String id; //MEMBER_ID 컬럼 사용
Team team; //참조로 연관관계를 맺는다. (Long 타입의 id가 아닌 Team 타입의 객체가 들어가는게 객체지향 답다.)
String username;//USERNAME 컬럼 사용
Team getTeam() {
return team;
}
}
class Team {
Long id; //TEAM_ID PK 사용
String name; //NAME 컬럼 사용
}
수정한 객체 모델로 테이블에 저장할 때
class Member {
String id; //MEMBER_ID 컬럼 사용
Team team; //참조로 연관관계를 맺는다.
String username;//USERNAME 컬럼 사용
Team getTeam() {
return team;
}
}
// insert SQL (TEAM_ID를 넣기 위해 `member.getTeam().getId()` 를 통해 호출)
/* INSERT INTO MEMBER(MEMBER_ID, TEAM_ID, USERNAME) VALUES ... */
수정한 객체 모델로 테이블에서 조회할 때 (문제발생)
// select SQL
/*
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;
}
/* SQL 결과를 사용하기위해 객체를 생성하고.. 객체에 set하고.. 너무 번잡하다.*/
이러한 과정을 자바 컬렉션에서 관리한다면? (해결 가능성)
- 조회
list.add(member);
- 저장
Member member = list.get(memberId);
Team team = member.getTeam();
- 전부 코드 한줄로 되어서 매우 간편하다!
- 하지만 해당 모델링을 RDB에 넣는 순간 매우 복잡해지며 생산성이 나오지 않는다.
객체 그래프 탐색에서의 문제
- 객체는 자유롭게 객체 그래프를 탐색할 수 있어야 한다.
member.getTeam()
,member.getOrder().getDelivery()
등
그러나 처음 실행하는 SQL에 따라 탐색 범위가 결정 된다.
/*
SELECT M.*, T.*
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
*/
member.getTeam(); //OK
member.getOrder(); //null
-
쿼리결과에 따라 member 객체의 team은 가져올 수 있으나 order 객체는 불가능 (온전한 객체 그래프 탐색 불가능)
- 결과적으로 엔티티에 대한 신뢰 문제가 발생함.
class MemberService { ... public void process() { Member member = memberDAO.find(memberId); member.getTeam(); //??? member.getOrder().getDelivery(); // ??? } }
member.getTeam();
,member.getOrder().getDelivery();
- 맞는 결과를 가져올 수 있다고 장담할 수 없다!
memberDAO.find(memberId);
- 해당 메소드 내에서 어떤 동작(쿼리, 객체 세팅 등)이 이루어 지는지 직접 눈으로 확인하기 전까지 알 수 없기 때문이다.
- 신뢰할 수 없다!
- 그렇다고 모든 객체를 미리 로딩할 수는 없다.
emberDAO.getMember(); //Member만 조회 memberDAO.getMemberWithTeam(); //Member와 Team 조회 memberDAO.getMemberWithOrderWithDelivery(); //Member,Order,Delivery 조회
- 다음과 같이 상황에 따라 해당 객체의 조회 메서드를 여러개 생성한다?
- 너무 많은 메소드를 생성해야 하며 복잡하다.
- 이런식으로 많은 메소드를 만들어 SQL을 직접 다루게 되면 계층형 아키텍처 진정한 의미의 계층 분할이 어렵다.
- 물리적으로는 Service, DAO등으로 계층이 나뉘어져 있지만 논리적으로는 강결합 되어있다.
- 다음과 같이 상황에 따라 해당 객체의 조회 메서드를 여러개 생성한다?
객체 간 비교할 때 문제
String memberId = "100";
Member member1 = memberDAO.getMember(memberId);
Member member2 = memberDAO.getMember(memberId);
member1 == member2; //다르다.
class MemberDAO {
public Member getMember(String memberId) {
String sql = "SELECT * FROM MEMBER WHERE MEMBER_ID = ?";
...
//JDBC API, SQL 실행
return new Member(...);
}
}
member1 == member2
는false
다.- 쿼리로 가져온 결과를
new Member(..);
로 다시 반환하기 때문에 당연히 결과가 다르다.
- 쿼리로 가져온 결과를
- 자바 컬렉션에서 조회하여 비교한다면? (해결 가능성)
String memberId = "100"; Member member1 = list.get(memberId); Member member2 = list.get(memberId); member1 == member2; //같다.
- 컬렉션에서 조회하여 갖고 오기 때문에 참조값이 같아서
true
반환
결과적으로..
- 이렇듯 객체답게 모델링 하면 할수록 매핑 작업만 늘어난다.
- 객체를 자바 컬렉션에 저장 하듯이 DB에 저장할 수 없을까??
- 그 고민의 결과로 자바 진영에서 JPA (Java Persistence API) 가 탄생하게 된다.
📣 Reference
본 포스팅은 김영한님의 강의를 듣고 스스로 정리 및 추가한 내용입니다.
댓글남기기