DEV ℧ Developer Diary

[JPA] SQL 중심적인 개발의 문제점

SQL 중심적인 개발의 문제점

해당 포스트는 인프런 김영한님의 자바 ORM 표준 JPA 프로그래밍 - 기본편 을 듣고 정리한 글입니다.

기존 SQL 위주 개발의 문제점

현대 애플리케이션의 언어는 Java, Scala 등등 객체 지향의 언어를 사용하고 있다. 하지만 데이터베이스의 경우 Oracle, MySQL 같은 관계형 DB를 주로 사용한다.

이로 인해 객체지향의 언어관계형 DB를 통해 관리되고 있다.

이로인해 객체지향보단 관계형 DB를 위한 SQL 위주의 개발이 우선되고 있다.

1. 무한 반복, 지루한 코드

  • CRUD

아래와 같은 반복적인 SQL 코드와 자바 객체 -> SQL, SQL -> 자바 객체과 같은 반복적인 코드가 많아진다.

INSERT INTO ...
UPDATE ...
SELECT ...
DELETE ...
  • 필드추가

필드가 추가될 경우 자바의 객체 뿐만 아니라 관련된 CRUD의 SQL코드도 변경이 되어야함.

class Member {
    private String id;
    private String password;
    private String name;
    /* 변수 추가 */
    private String email;
}
INSERT INTO MEMBER (ID, PASSWORD, NAME, EMAIL(추가)) ...
SELECT ID ... EMAIL(추가) FROM MEMBER ...
UPDATE MEMBER SET ID ... EMAIL(추가) ...

결국 SQL의존적인 개발을 피하기가 어렵다.

2. 패러다임의 불일치

1. 비교

  • 객체 VS 관계형 데이터베이스

객체 지향 프로그래밍에서 제공하는 추상화, 캡슐화, 정보은닉, 상속, 다양성 등 객체지향의 특징을 관계형 데이터 베이스에 적용하기가 어렵다.

jpa의 sql 변환

객체의 SQL 변환 및 SQL 작성 등 SQL 에 많은 시간이 소요

  • 객체와 관계형 데이터베이스 차이
  1. 상속 (객체 O 데이터베이스 X)
  2. 연관관계 (객체 reference 데이터베이스 PK, FX)
  3. 데이터 타입
  4. 데이터 식별 방법

2. 상속

상속

DB를 논리적으로 객체와 같이 설계 할 경우 ITEM과 자식 객체들의 SQL이 모두 작성되어야 한다.

EX) Album의 조회

  1. 각각의 테이블에 따른 조인 SQL 작성
  2. 각각의 객체 생성
  3. 상상만 해도 복잡…
  4. 더 이상의 설명은 생략…
  5. 그래서 DB에 저장할 객체에는 상속관계 안쓴다
  • 자바 컬렉션에서 조회한다면?
Album album = list.get(albumId);

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

3. 연관관계

객체는 참조를 사용한다. ex) member.getTeam() 테이블은 외래 키(FK)를 사용 ex) JOIN ON M.TEAM_ID = T.TEAM_ID

연관관계

위의 예시의 테이블 연관관계를 이용해 아래 INSERT SQL을 작성해보고자 한다.

INSERT INTO MEMBER(MEMBER_ID, TEAM_ID, USERNAME) VALUES ...
  • 객체를 테이블에 맞추어 모델링 했을 경우?
class Member {
    /* MEMBER_ID 컬럼 */
    String id;
    /* TEAM_ID FX 컬럼 */
    Long teamId;
    /* USERNAME 컬럼 */
    String username;
}

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

자바 객체를 관계형 DB의 테이블에 맞춰서 설계가 진행 되고 SQL을 통해 작성

  • 객체지향적인 모델링을 했을 경우?
class Member {
    /* MEMBER_ID 컬럼 */
    String id;
    /* 참조를 통해 연관관계를 맺는다. */
    Team team;
    /* USERNAME 컬럼 */
    String username;

    Team getTeam() {
        return team;
    }
}

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

Member 클래스를 이용해 MEMBER 테이블에 데이터를 넣기 위해서는 member.getTeam().getId();와 같이 Team 클래스의 id를 또 불러와야 한다.

객체 지향적 설계를 진행 했을 경우 추가적인 로직이 발생했지만 큰 문제는 발생하지 않았다. 하지만 조회쿼리에서 문제가 발생한다.

객체 지향적 설계에서 JOIN을 이용해 MEMBER 테이블과 TEAM 테이블의 데이터를 불러와보자.

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와 Team의 조회를 위해서는 복잡한 로직이 추가된다.

해당 SQL을 통한 INSERT와 SELECT를 객체 모델링, 자바 컬렉션에서 관리한다면 아래와 같이 짧은 코드로 진행할 수 있다.

/* SQL INSERT */
list.add(member);

/* SQL SELECT */
Member member = list.get(memberId);
Team team = member.getTeam();

4. 객체 그래프 탐색

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

객체 그래프

하지만 SQL을 이용한다면, 처음 실행하는 SQL에 따라 탐색 범위가 결정된다.

SELECT M.*, T.*
FROM MEMBER M
 JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID

위와 같은 SQL을 실행해서 자바 객체에 데이터를 넣어줄 경우

member.getTeam(); 는 데이터를 조회해서 값을 가져올 수 있지만, member.getOrder(); 는 데이터가 SQL에서 조회해 오지 않기 때문에 null이다.

위와 같은 문제는 엔티티에 대한 신뢰문제 가 생길 수 있다.

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

member에서 조회해 오는 객체들을 직접 까서 탐색하지 않는 이상 신뢰 할 수 없다.

또한 모든 객체를 미리 로딩할 수는 없다. 상황에 따라 동일한 회원 조회 메서드를 여러벌 생성해야 한다.

memberDAO.getMember(); : Member만 조회 memberDAO.getMemberWithTeam(); : Member와 Team 조회 memberDAO.getMemberWithOrderWithDelivery(); : Member, Order, Delivery 조회

5. 비교하기

  • SQL
class MemberDAO {
    public Member getMember(String memberId) {
        String sql = "SELECT * FROM MEMBER WHERE MEMBER_ID = ?";
        ...
        /* JDBC API, SQL 실행 */
        return new Member(...);
    }
}

String memberId = "100";
Member member1 = memberDAO.getMember(memberId);
Member member2 = memberDAO.getMember(memberId);

SQL을 통해 MEMBER테이블의 정보를 위와 같이 member1과 member2를 같은 memberId를 통해 각각 호출하여도 member1 == member2;를 실행한다면 false 의 결과를 얻을 수 있다.

  • 자바 컬렉션
String memberId = "100";
Member member1 = list.get(memberId);
Member member2 = list.get(memberId);

하지만 자바 컬렉션에서 같은 memberId를 이용해 컬렉션을 꺼내준다면 member1 == member2;true의 결과를 얻는다.

이를 통해 자바의 객체지향으로 다룰때와 관계형 DB를 다룰때 서로 맞지 않는 흐름이 있다고 볼 수 있다.

객체답게 모델링을 할수록 매핑 작업만 늘어나게된다.