본문 바로가기

스프링/Spring Data

SQL 중심적인 개발의 배경과 문제점

반응형
 
 

들어가며

이 포스팅의 내용은 «자바 ORM 표준 JPA 프로그래밍»을 참고하고 정리한 것입니다.

 

SQL 중심적인 개발의 배경

  • JPA와 모던 자바 데이터 저장 기술
    • 애플리케이션 객체 지향 언어 (Java, Scala 등) + 관계형 DB (Oracle, MySQL 등)
    • 객체를 영구 보관하는 다양한 저장소 (RDB, NoSQL, File, OODB 등)가 존재하지만 현실적인 대안은 관계형 DB다.
  • 즉, 객체를 관계형 DB에 저장해서 관리하는 것이 중요하다.
    • 관계형 DB를 사용하려면 SQL을 짤 수 밖에 없다.
    • 관계형 DB를 쓰는 상황에서는 SQL에 의존적인 개발을 피하기 어렵다.
  • 하지만! SQL 중심적인 개발에는 여러 문제점이 있다.

 

SQL 중심적인 개발의 문제점

지루한 코드의 무한 반복

  • CRUD의 반복
  • 자바 객체를 SQL로, SQL을 자바 객체로 변환하는 과정의 반복
  • 예시)
    • 회원에 나이 정보를 추가하고자 한다.
    • Member 클래스에 'private int age'를 추가한다.
    • INSERT, SELECT, UPDATE 등 관련된 모든 쿼리에 age 정보를 추가한다.

객체 지향과 관계형 데이터베이스 간의 패러다임 불일치

  • 객체 지향
    • 필드와 메서드 등을 묶어서 잘 캡슐화해서 사용하는 것이 목표
    • 객체 지향 프로그래밍은 추상화, 캡슐화, 정보은닉, 상속, 다형성 등 시스템의 복잡성을 제어할 수 있는 다양한 장치들을 제공한다.
  • 관계형 데이터베이스
    • 데이터를 잘 정규화해서 보관하는 것이 목표
  • 패러다임이 다른 두 가지를 가지고 억지로 매핑하기 때문에 여러 가지 문제가 발생한다.
    • Object를 RDB에 넣으려고 하니까 문제가 발생한다.
  • 하지만, RDB가 인식할 수 있는 것은 SQL뿐이기 때문에 결국 SQL 작업이 많다.
    • Object -> [SQL 변환] -> RDB에 저장

객체(Object)와 관계형 데이터베이스(RDB)의 차이

상속

  • Object
    • 상속 관계가 있다.
  • RDB
    • 상속 관계가 없다
    • Object의 상속 관계와 유사한 물리 모델로, Table 슈퍼타입-서브타입 관계가 존재한다.

 

  • Album 객체를 DB에 저장하는 과정
    1. 객체를 분해한다. (Album 객체는 Item의 속성을 모두 가짐)
    2. 각각 다른 테이블에 대한 INSERT 쿼리를 두 번 날린다.
INSERT INTO ITEM ...
INSERT INTO ALBUM ...
  • Album 을 조회하는 과정 (문제!!!)
    1. 각각의 테이블에 따른 Join SQL을 작성한다. (Item과 Album을 Join해서 데이터를 가져온다.)
    2. 각각의 객체를 생성하고 모든 필드 값을 세팅한다.(Item과 Album 각각 모든 필드값을 세팅한다.)
    3. Movie, Book을 조회하고 싶으면 위의 과정을 또 반복해야 한다.
DB에 저장할 객체에는 상속 관계를 사용하지 않는다.
  • 자바 컬렉션에 저장하면?
    • 저장: list.add(album);
    • 조회: Album album = list.get(albumId);
    • 다형성 활용: Item item = list.get(albumId);
      • 심지어 객체이기 때문에 필요하면 부모 타입으로 조회한 후 다형성으로 활용할 수도 있다.
  • 자바 컬렉션에 저장하면 굉장히 단순한 작업이 관계형 데이터베이스에 넣고 빼는 순간, 중간중간의 Object와 RDB의 매핑 작업을 개발자가 직접 해줘야 하기 때문에 굉장히 번잡한 일이 된다.

연관 관계

  • Object
    • 참조(Reference)를 사용하여 연관 관계를 찾는다.
      • Ex) member.getTeam();
    • 단방향으로만 관계가 존재한다.
      • Member -> Team은 가능하지만, Team -> Member는 불가능하다.
  • RDB
    • 외래 키(FK)를 사용하여 Join 쿼리를 통해 연관 관계를 찾는다.
      • Ex) JOIN ON M.TEAM_ID = T.TEAM_ID
    • 양방향으로 모두 조회가 가능하다. 즉, 단방향이 존재하지 않는다.
      • Member -> Team 가능: MEMBER.TEAM_ID(FK)와 TEAM.ID(PK)를 Join
      • Team -> Member 가능: TEAM.ID(PK)와 MEMBER.TEAM_ID(FK)를 Join

데이터 타입

데이터 식별 방법

 

모델링 과정에서의 문제

- 객체를 테이블에 맞추어 모델링

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

class Team {
    Loing id; //TEAM_ID PK 사용
    String name; //NAME 칼럼사용
}

객체의 참조값 자체가 필드에 들어가는 것이 더 객체지향스럽다고 할 수 있다.

- 객체지향 모델링

class Member {
    String id; // MEMBER_ID 칼럼사용
    Team team; // 참조로 연관관계를 맺는다.
    String username; //USERNAME 칼럼사용
}

class Team {
    Loing id; //TEAM_ID PK 사용
    String name; //NAME 칼럼사용
}
  • Team 객체의 참조값 자체를 필드에 넣는다.
    • Team team;
  • 이렇게 설계된 객체를 DB에 저장하려면,
    • member.getTeam().getId() 로 값을 얻어와 TEAM_ID에 넣는다.
    • INSERT INTO MEMBER(MEMBER_ID, TEAM_ID, USERNAME) VALUES ...
  • Member 객체를 조회하려면,
    1. MEMBER와 TEAM을 Join 해서 데이터를 한 번에 모두 가져온다.
    2. Member와 Team에 대한 값을 각각 모두 세팅한다.
    3. member.setTeam(team);과 같이 직접 연관 관계를 맺어 준다.
    4. 해당 member 객체를 반환한다.

객체 그래프 탐색에서의 문제

  • 객체는 자유롭게 객체 그래프(연관 관계가 있는 객체 사이)를 탐색할 수 있어야 한다.
    • member.getTeam(), member.getOrder().getOrderItem() 등 …
  • 하지만, 서비스 로직에서 RDB와 연결된 데이터를 탐색할 때 객체 그래프를 탐색할 수 없다.
    • Why? 처음 실행하는 SQL에 따라 탐색 범위가 결정되기 때문이다.
    • Ex) 처음 SQL에서 Member와 Team을 가져왔다고 하면, Order는 가져오지 않았기 때문에 member.getOrder()는 null이 된다.
SELECT M.*, T.*
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID

member.getTeam();  // OK
member.getOrder(); // null

“비교하기” 에서의 차이

- 일반적인 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(...); 
    }
}
  • 식별자가 같아도 DAO의 getMember()에서
    • 각각에 대해서 SELECT Query를 날리고 (두 번 날림)
    • new Member()로 객체를 생성하기 때문에 당연히 다르다.

- 자바 컬렉션에서 조회하는 경우

String memberId = "100";
Member member1 = list.get(memberId);
Member member2 = list.get(memberId);
member1 == member2; // 같다!!! (참조값이 같다.)

식별자가 같을 때 컬렉션에서의 두 객체의 참조값은 같기 때문에 두 객체는 같다.

마무리

  • 객체답게 모델링할수록 매핑 작업만 늘어난다.
  • 객체를 자바 컬렉션에 저장하고 불러오듯이 DB에 저장할 수는 없을까?
    • 이 고민의 결과가 바로 JPA(Java Persistence API)이다
  •  
반응형

'스프링 > Spring Data' 카테고리의 다른 글

Hello JPA!! 애플리케이션 개발하기  (0) 2021.05.20
JPA 소개 및 JPA의 기본 동작 과정  (0) 2021.05.20