1. SQL 중심적인 개발의 문제점
- Java/자바 ORM 표준 JPA 프로그래밍 - 기본편
- 2022. 1. 31.
💡 인프런의 김영한 선생님 강의를 정리한 글입니다.
객체 지향 언어와 관계형 데이터 베이스
객체 지향 언어
를 이용한 애플리케이션은 보통 관계형 데이터 베이스
에서 관리한다.
그러다 보니 SQL 중심적인 개발이 주를 이루게 된다. (CRUD → 지루한 코드를 무한 반복 함)
예를 들어 회원 객체를 만들었다면, 그에 해당하는 SQL을 짜게 되는데
만약 개발하다가 필드가 추가되면 짜놓은 SQL 쿼리들을 하나씩 다 수정해야 한다..
즉, SQL에 의존적인 개발을 피하기 어렵다.
또, 객체와 관계형 데이터 베이스 사이의 패러다임의 불일치
가 발생한다.
🤔 객체를 어떤 저장소에 보관해야 할까?
RDB, NoSQL, FILE 등이 있는데, 현실적으로 FILE에 넣으면 검색
을 할 수 없다.
NoSQL은 대안이 될 수 있지만, 아직 메인이 아니다.
➡️ 현실적인 대안으로 관계형 데이터 베이스를 써야 한다.
🤣 객체를 SQL로 변환 해야 한다.
객체를 SQL로 바꿔서 RDB에 저장해야 하는데
이 객체를 SQL로 바꾸는 작업을 개발자
가 하게 된다.. (개발자 = SQL Mapper..)
객체와 관계형 데이터 베이스의 차이
1. 상속
객체 → 상속 관계
관계형 데이터 베이스 → 슈퍼타입, 서브타입
관계형 데이터베이스
의 경우, 예를들어 ALBUM을 저장하려면, ITEM과 ALBUM테이블에 저장할 쿼리 2개를 만들어야 한다.
삽입의 경우에는 크게 복잡하진 않지만, 조회의 경우는 다르다.
ALBUM을 조회하고 싶다면, ITEM과 ALBUM 테이블을 JOIN해서 ALBUM과 ITEM 각 객체에 맞게 데이터를 집어 넣어 줘야한다.
이렇게 객체가 상속 관계로 맵핑되어 있다면, 데이터 베이스에서 매번 여러 테이블을 JOIN하여 데이터를 가져와야 한다.
➡️ 결론 : DB에 저장할 객체에는 상속 관계를 쓰지 않는다.
반대로, 객체
, 즉 자바의 컬렉션을 사용할 경우,
list.add(album);
Album album = list.get(albumId);
// 부모 타입으로 조회 후 다형성 활용
Item item = list.get(albumId);
➡️ list.get(albumId)으로, 앨범 아이디만으로 아이템을 조회 해올 수 있다. (다형성을 통해 부모 타입인 Item을 가져올 수 있다)
2. 연관 관계
객체 → 참조(reference)로 연관관계를 찾는다. ex) memger.getTeam()
테이블 → 외래키로 연관관계를 찾는다. ex) join on m.team_id = t.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 컬럼 사용
}
멤버 객체에 팀 객체의 외래키를 가져오도록 모델링 하였음.
근데 위에서 객체는 참조로 연관관계를 맺는다고 정답을 말했다.
객체 지향적인 설계를 위해서는 멤버 객체에 팀 객체의 참조값을 가지는게 당연한 생각이다.
(그래야 member.getTeam()으로 연관관계를 찾아올 수 있다.)
객체 다운 모델링을 하면 다음과 같다.
class Member {
String id; // MEMBER_ID 컬럼 사용
Team team; // 참조로 연관관계를 맺는다.
String username;// USERNAME 컬럼 사용
Team getTeam() {
return team;
}
}
class Team {
Long id; // TEAM_ID PK 사용
String name; // NAME 컬럼 사용
}
다음과 같이 모델링하면 db에 값을 저장할 때 member 데이터와, 외래키 값을 저장해야 한다.
멤버 객체에는 팀의 참조값만 가지고 있기 떄문에, member.getTeam().getId()
로 팀의 pk를 가져와서 저장하면 된다.
저장은 위 방식으로 해결이 가능함.
근데 또 조회
의 경우에 문제가 생긴다.
예를들어,
class MemberService {
...
public void process() {
Member member = memberDAO.find(memberId);
member.getTeam(); //???
member.getOrder().getDelivery(); // ???
}
}
다음과 같이 멤버의 값을 조회하고, 멤버가 속해있는 팀을 찾아 오는 비즈니스 로직이 있다고 생각해 보자.
객체는 원래 그래프 탐색
이 가능해야 한다. → get을 통해 참조된 모든 객체를 탐색 할 수 있다.
하지만, 위 로직에서 memberDAO.find(memberId);
이 구문이 어떤 SQL을 날리는지 우리는 알지 못한다.
(memberDAO의 find 메서드를 구현한 사람만 안다..)
SQL이 실행된 순간 부터 조회해온 테이블의 범위가 정해져 있기 때문에, 멤버 객체가 모든 객체들을 get으로 탐색할 수 있다는 보장이 없다.
예) 처음 실행하는 SQL에 따라 탐색 범위 결정
SELECT M.*, T.* FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
member.getTeam(); //OK
member.getOrder(); //null
참고 : Layerd-Architecture : 계층 간에는 신뢰가 존재해야 하는데, 위의 경우 Entity 간의 신뢰를 할 수 없다.
(물리적으로는 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(...);
}
}
SQL로 조회해온 값들을 결국 새로운 멤버 인스턴스를 반환 시키기 때문에 member1과 member2의 참조가 다르게 된다.
하지만 자바의 컬렉션
에서 멤버를 조회한다고 생각하면
String memberId = "100";
Member member1 = list.get(memberId);
Member member2 = list.get(memberId);
member1 == member2; // 같다.
조회를 위한 id (key 값)의 같기 때문에 member1과 member2는 같은 객체이다.
결론 : 객체 답게 모델링 할 수록 SQL ↔️ 객체 맵핑 작업이 늘어난다.
객체를 자바의 컬렉션에 저장하듯이, 관계형 데이터베이스에 저장할 수 없을까? 라는 생각의 결과물로 JPA
가 등장하게 됨.
'Java > 자바 ORM 표준 JPA 프로그래밍 - 기본편' 카테고리의 다른 글
5. 엔티티 매핑 (0) | 2022.03.20 |
---|---|
4. 영속성 관리 - 내부 동작 방식 (0) | 2022.03.19 |
3. JPA 시작 (0) | 2022.03.12 |
2. JPA란 무엇인가? (0) | 2022.02.20 |