[Spring] 스프링 DB 2편 5-2. ORM 개념1 - SQL 중심적인 개발의 문제점

강우엉·2023년 8월 31일

Spring

목록 보기
312/359

💡 배경

현재 대부분 현대적인 애플리케이션은 객체 지향 언어를 사용한다.
또한 데이터를 저장하기 위해서는 관계형 DB를 사용한다.

결론적으로 지금 시대는 객체를 관계형 DB에 관리한다.

문제점이 있다.
애플리케이션은 객체지향적으로 설계하는데 DB는 SQL만 알아듣기 때문에 SQL을 계속 작성해서 날려줘야 한다. 이러면 바로 SQL 중심적인 개발을 하게 된다.

💡 SQL 중심적인 개발의 문제점

무한 반복, 지루한 코드

  • 기능 하나 추가해서 테이블을 하나 만들더라고, CRUD 쿼리를 다 짜야한다.
  • 또 자바 객체를 SQL로 바꾸고, SQL을 자바 객체로 바꾸는 등의 반복적인 작업을 해야함

객체 CRUD

예를 들어서, 기획자가 회원을 설계해달라 하였다.
우리는 회원 테이블 만들고, 회원 객체 만들고, 쿼리를 작성할것이다.

그렇게 다 만들었는데 갑자기 연락처 컬럼을 추가해달라고 한다면?
객체, 테이블, 쿼리를 전부 수정해야하는 번거로움이 생긴다.

결국, 관계형 DB를 사용하는 이 상황에서는 SQL에 의존적인 개발을 피하기 어렵다.

💡 패러다임의 불일치

객체 vs 관계형 데이터베이스

SQL 작성문제를 넘어서서 패러다임의 불일치라는 문제가 있다.

  • 관계형 DB는 데이터를 잘 정규화해서 보관하는 것이 목표
  • 객체는 속성과 메서드로 잘 캡슐화해서 사용하는 것이 목표

객체를 영구 보관하는 다양한 저장소

객체가 먼저있고, 객체를 저장할 저장소를 고른다고 생각해보자.

현실적으로 관계형 DB를 대체할만한건 없다.

현실적인 대안은 관계형 데이터베이스

객체를 관계형 데이터베이스에 저장


객체를 RDB에 저장하기 위해서는 SQL로 바꿔야한다. 결국 SQL을 짜야하는데 그것을 다 개발자들이 한다는 것이다.

개발자 = SQL 매퍼

💡 객체와 관계형 데이터베이스의 차이

  1. 상속
  2. 연관관계
  3. 데이터 타입
  4. 데이터 식별 방법

상속


객체의 상속관계를 관계형 DB에 넣는것은 매우 힘들다.
슈퍼타입, 서브타입에 저장한다해도, 조회할때 쉽지 않다.

Album 조회
1. 각각의 테이블에 따른 조인 SQL 작성...
2. 각각의 객체 생성...
3. 상상만해도 복잡
4. 더 이상의 설명은 생략...
5. 그래서 DB에 저장할 객체에는 상속관계 안쓴다

만약 객체를 자바 컬렉션에 저장하고 조회한다면?

list.add(album);

Album album = list.get(albumId);

//부모 타입으로 조회 후 다형성 활용
Item item = list.get(albumId);

단순하게 가능해진다.

연관관계


객체는 참조로 연관된 객체를 찾아갈 수 있다.
그런데 테이블은 외래키 사용함. MEMBER에서 TEAM 가고싶다면, MEMBER에 있는 TEAM_ID(FK)와 TEAM의 PK를 JOIN하면 된다.

그런데 객체는 Member에서 Team으로는 갈 수 있지만, 역으로 Team에서 Member로는 가지 못한다. (반대방향으로 참조가 없기 때문)

하지만 테이블의 경우 PK와 FK로 JOIN하는 것이기 때문에 양방향으로 가능하다.

객체를 테이블에 맞추어 모델링
따라서 보통 객체를 테이블에 맞추어 모델링을 한다.

class Member {
    String id; //MEMBER_ID 컬럼 사용
    Long teamId; //TEAM_ID FK 컬럼 사용
    String username; //USERNAME 컬럼 사용
}

class Team {
    Long id; //TEAM_ID PK 사용
    String name;  //Name 컬럼 사용
}

테이블에 맞춘 객체 저장

class Member {
    String id; //MEMBER_ID 컬럼 사용
    Long teamId; //TEAM_ID FK 컬럼 사용
    String username; //USERNAME 컬럼 사용
}
INSERT INTO MEMBER(MEMBER_ID, TEAM_ID, USERNAME) VALUES ...

객체지향스러운 설계가 아니다.

객체다운 모델링

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 컬럼 사용
}

객체 모델링 저장

class Member {
    String id; //MEMBER_ID 컬럼 사용
    Team team; // "참조로 연관관계를 맺는다!!!!"
    String username;//USERNAME 컬럼 사용
    
    Team getTeam() {
    	return team;
    }
}
   **  member.getTeam().getId(); **

INSERT INTO MEMBER(MEMBER_ID, TEAM_ID, USERNAME) VALUES...

테이블에 필드 매핑해서 넣어야하는데, 객체 모델링에는 외래키 값을 나타내는 필드가 없거 Team 객체에대한 참조만 존재함.
Team에 대한 참조를 타고가서 Id를 가져와서 FK로 넣어준다.

하지만 객체지향 모델링의 문제점은 조회에서 발생한다.

객체 모델링 조회

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();
    //DB에서 조회한 회원 관련 정보를 모두 입력
    Team team = new Team();
    //DB에서 조회한 팀 관련 정보를 모두 입력
    
    //회원과 팀 관계 설정
    member.setTeam(team);
    return member;
}

조인해서 가져온 회원과 팀 각각의 정보를 각각의 객체에 넣어주고, 마지막으로 setTeam(team)으로 연관관계 설정까지 개발자가 해줘야한다.

굉장히 번거롭다. SQL중심으로 설계하면 그냥 바로 맞는 필드에 때려넣으면 되는데, 구분해서 가져온 후 연관관계까지 설정해줘야한다.

따라서 보통 member랑 team 함께 조회할 경우 MemberTeam이라는 큰 클래스(DTO) 만들어서 응답값 일자로 넣는다. 그리고 객체하나로 반환하고 이런식으로 많이 한다.

객체 모델링, 자바 컬렉션에 관리
자바 컬렉션으로 관리한다하면, 객체지향적으로 설계하는 것이 좋아진다.

list.add(member);

Member member = list.get(memberId);
Team team = member.getTeam();

코드가 간단해진다.

객체 그래프 탐색
객체는 자유롭게 객체 그래프를 탐색할 수 있어야한다.

서로 참조가 있는 객체들은 자유롭게 해당 참조를 타고 조회 가능해야한다.
member.getTeam()
member.getOrder().getDelievery() 이런식으로

하지만, 처음 실행하는 SQL에 따라 탐색범위가 결정되버려서 위에 그림처럼 될 수 없다.

SELECT M.*, T.*
  FROM MEMBER M
  JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
member.getTeam(); //OK
member.getOrder(); //null

처음 SQL에서 member랑 team만 가져온다. 따라서 oreder는 null값이 된다.

엔티티 신뢰 문제로 이어진다.

class MemberService {
    public void process() {
        Member member = memberDAO.find(memberId);
        member.getTeam(); //???
        member.getOrder().getDelievery(); //???
    }
}

memberDAO를 통해 조회해온 member 엔티티에 Team과 Order, 그리고 Delievery라는 연관관계들 있지만, memberDAO에서 해당 데이터를 조회해왔는지 확실히 알지못하기 때문에 자유롭게 호출할 수 없다.

그렇다고해서 모든 객체를 미리 로딩할 수는 없다

member 조회할 때마다 모든 연관된 객체 다 끌고올 수도 없다.

따라서 대안으로 상황에 따라 동일한 회원 조회 메서드를 여러벌 생성한다.

memberDAO.getMember(); //Member만 조회
memberDAO.getMemberWithTeam(); //Member와 Team 조회
memberDAO.getMemberWithOrderWithDelievery(); //Member, Order, Delievery 조회

SQL을 직접 다루게 되면, 계층형 아키텍쳐에서 진정한 의미의 계층 분할이 어렵다.

비교하기

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을 사용해서 조회시, 반환되는 객체는 ==비교했을 때 다르다

비교하기 - 자바 컬렉션에서 조회

String memberId = "100";
Member member1 = list.get(memberId);
Member member2 = list.get(memberId);

member1 == member2; //같다

자바 컬렉션에서 같은 key값으로 조회한 객체는 동일하다.

정리

객체를 자바 컬렉션에 저장하듯이 DB에 저장할 수는 없을까?

자바 진영에서는 이러한 고민의 결과로 JPA(Java Persistence API)가 나옴

profile
우엉이의 코딩 성장일기💻

0개의 댓글