JPA

JPA를 사용해야하는 이유

자바지기 2022. 11. 8. 11:44
반응형

모락 프로젝트를 진행하면서 JPA를 사용했지만, 사용해야 하는 이유에 대해서 생각을 정리해보지 못했다.

단순히 SQL을 직접 작성하지 않아도 되기 때문에 사용했다고 생각해왔다. 

 

JPA를 왜 사용해야 할까?

여러 가지 이유들이 있다. 

 

1.  SQL을 직접 작성하지 않아도 된다.

JPA는 실행 시점에 자동으로 SQL을 만들어서 실행하므로 직접 작성하지 않아도 된다.

 

그렇다면 SQL을 직접 작성한다면 어떤 문제가 발생할까?

 

첫 번째로 SQL에 의존적인 개발을 하게 된다.

현재 모락 테이블은 아래와 같다.

더보기

모락 테이블 스키마

CREATE TABLE member
(
    `id`          BIGINT       NOT NULL AUTO_INCREMENT,
    `oauth_id`    VARCHAR(255) NOT NULL UNIQUE,
    `name`        VARCHAR(255) NOT NULL,
    `profile_url` VARCHAR(255) NOT NULL,
    `created_at`  DATETIME     NOT NULL,
    `updated_at`  DATETIME     NOT NULL,
    PRIMARY KEY (id)
);

CREATE TABLE team
(
    `id`         BIGINT       NOT NULL AUTO_INCREMENT,
    `name`       VARCHAR(255) NOT NULL,
    `code`       VARCHAR(255) NOT NULL UNIQUE,
    `created_at` DATETIME     NOT NULL,
    `updated_at` DATETIME     NOT NULL,
    PRIMARY KEY (id)
);

CREATE TABLE team_member
(
    `id`         BIGINT   NOT NULL AUTO_INCREMENT,
    `team_id`    BIGINT   NOT NULL,
    `member_id`  BIGINT   NOT NULL,
    `created_at` DATETIME NOT NULL,
    `updated_at` DATETIME NOT NULL,
    PRIMARY KEY (id),
    FOREIGN KEY (team_id) REFERENCES team (id),
    FOREIGN KEY (member_id) REFERENCES member (id)
);

CREATE TABLE team_invitation
(
    `id`         BIGINT       NOT NULL AUTO_INCREMENT,
    `team_id`    BIGINT       NOT NULL,
    `code`       VARCHAR(255) NOT NULL UNIQUE,
    `expired_at` DATETIME     NOT NULL,
    `created_at` DATETIME     NOT NULL,
    `updated_at` DATETIME     NOT NULL,
    PRIMARY KEY (id),
    FOREIGN KEY (team_id) REFERENCES team (id)
);

CREATE TABLE poll
(
    id             BIGINT       NOT NULL AUTO_INCREMENT,
    allowed_count  INT          NOT NULL,
    anonymous      BOOLEAN      NOT NULL,
    code           VARCHAR(255) NOT NULL,
    host_id        BIGINT       NOT NULL,
    status         VARCHAR(255) NOT NULL,
    team_code      VARCHAR(255) NOT NULL,
    title          VARCHAR(255) NOT NULL,
    closed_at      DATETIME     NOT NULL,
    selected_count INTEGER      NOT NULL DEFAULT 0,
    created_at     DATETIME     NOT NULL,
    updated_at     DATETIME     NOT NULL,
    PRIMARY KEY (id),
    FOREIGN KEY (team_code) REFERENCES team (code)
);

CREATE TABLE poll_item
(
    id      BIGINT NOT NULL AUTO_INCREMENT,
    subject VARCHAR(255),
    poll_id BIGINT NOT NULL,
    PRIMARY KEY (id),
    FOREIGN KEY (poll_id) REFERENCES poll (id)
);

create table select_member
(
    poll_item_id BIGINT        NOT NULL,
    description  VARCHAR(1000) NOT NULL,
    member_id    BIGINT        NOT NULL,
    PRIMARY KEY (poll_item_id, member_id),
    FOREIGN KEY (poll_item_id) REFERENCES poll_item (id),
    FOREIGN KEY (member_id) REFERENCES member (id)
);

CREATE INDEX `poll_index_closed_at` ON `poll` (`closed_at`);
CREATE INDEX `poll_index_code` ON `poll` (`code`);

CREATE TABLE appointment
(
    `id`               BIGINT       NOT NULL AUTO_INCREMENT,
    `team_code`        VARCHAR(255) NOT NULL,
    `host_id`          BIGINT       NOT NULL,
    `title`            VARCHAR(255) NOT NULL,
    `sub_title`        VARCHAR(255) NOT NULL,
    `start_date`       DATE         NOT NULL,
    `end_date`         DATE         NOT NULL,
    `start_time`       TIME         NOT NULL,
    `end_time`         TIME         NOT NULL,
    `duration_minutes` INTEGER      NOT NULL,
    `status`           VARCHAR(255) NOT NULL,
    `code`             VARCHAR(255) NOT NULL UNIQUE,
    `closed_at`        DATETIME     NOT NULL,
    `created_at`       DATETIME     NOT NULL,
    `updated_at`       DATETIME     NOT NULL,
    PRIMARY KEY (id),
    FOREIGN KEY (team_code) REFERENCES team (code),
    FOREIGN KEY (host_id) REFERENCES member (id)
);

CREATE INDEX `index_appointment` ON `appointment` (`closed_at`);

CREATE TABLE appointment_available_time
(
    `appointment_id`  BIGINT   NOT NULL,
    `member_id`       BIGINT   NOT NULL,
    `start_date_time` DATETIME NOT NULL,
    `created_at`      DATETIME NOT NULL,
    FOREIGN KEY (appointment_id) REFERENCES appointment (id),
    FOREIGN KEY (member_id) REFERENCES member (id)
);

ALTER TABLE appointment_available_time
    ADD UNIQUE (appointment_id, member_id, start_date_time);

CREATE TABLE slack_webhook
(
    `id`         BIGINT       NOT NULL AUTO_INCREMENT,
    `team_id`    BIGINT       NOT NULL UNIQUE,
    `url`        VARCHAR(255) NOT NULL,
    `created_at` DATETIME     NOT NULL,
    `updated_at` DATETIME     NOT NULL,
    PRIMARY KEY (id),
    FOREIGN KEY (team_id) REFERENCES team (id)
);

CREATE TABLE role
(
    `id`         BIGINT       NOT NULL AUTO_INCREMENT,
    `team_code`  VARCHAR(255) NOT NULL,
    `created_at` DATETIME     NOT NULL,
    `updated_at` DATETIME     NOT NULL,
    PRIMARY KEY (`id`)
);

CREATE TABLE role_match_result
(
    `role_history_id` BIGINT       NOT NULL,
    `member_id`       BIGINT       NOT NULL,
    `role_name`       VARCHAR(255) NOT NULL,
    PRIMARY KEY (`role_history_id`, `member_id`, `role_name`)
);

CREATE TABLE role_name
(
    `role_id` BIGINT       NOT NULL,
    `name`    VARCHAR(255) NOT NULL
);

CREATE TABLE role_history
(
    `id`        BIGINT   NOT NULL AUTO_INCREMENT,
    `date_time` DATETIME NOT NULL,
    `role_id`   BIGINT   NOT NULL,
    PRIMARY KEY (`id`)
);

이렇게 많은 테이블에 대해서 반복적으로 CRUD를 쿼리를 작성했다면 굉장히 생산성이 떨어졌을 것이다.

 

또한 프로젝트를 진행하면서 테이블에 column을 추가하거나 삭제하는 일도 있었다.

SQL을 직접 작성했다면, 테이블의 변경이 발생할 때마다 여러 쿼리를 수정해야 하는 번거로운 작업을 진행했을 것이다.

이때 쿼리를 잘못 수정하거나 깜빡하고 수정하지 않은 부분이 생긴다면? 생산성에 엄청난 문제가 생길 것이다.

 

이렇게 개발을 진행하게 되면, 결론적으로 SQL에 의존적인 개발을 하게 된다.

 

개발자는 이런 생산성 문제를 해결하기 위해서 SQL 작성에 최적화된 객체를 만들게 될 것이다.

즉, 객체를 테이블에 맞춰서 설계를 하게 될 것이다.

 

객체 지향적 설계가 아닌 테이블 중심 설계를 통해 개발을 하게 되면, 유지보수에 어려움이 생긴다.

모락 프로젝트를 진행한 시간의 70% 정도는 유지 보수를 진행했었다.

많은 시간을 유지보수에 투자한 만큼 유지보수가 중요하다는 것을 몸으로 느꼈기 때문에, 최대한 테이블 중심 설계는 피해야 한다.

 

두 번째로 다른 RDBMS로 변경이 어렵다.

Sql을 직접 작성하면 다른 RDBMS로 변경이 어렵다.

현재 모락 프로젝트는 Mysql DB를 사용하고 있다. Mysql에서 Orcal DB을 사용하기로 정책이 변경된다면 어떻게 될까?

만약 SQL을 직접 작성했다면 수정해야 할 부분이 굉장히 많을 것이다. 

JPA를 사용한다면 실행 시점에 자동으로 SQL을 만들어서 실행하므로 DB가 변경되더라도 코드를 거의 수정하지 않아도 된다.

 

 

2. 객체와 RDBMS 사이의 패러다임 불일치 문제를 해결할 수 있다.

객체와 RDBMS는 지향하는 목적이 서로 다르다. 즉, 패러다임이 불일치한다.

개발자는 패러다임 불일치를 해결하기 위해 많은 비용을 소모해야 한다.

 

한 가지 예를 들면, 객체는 연관 관계를 가질 때 참조에 접근해서 연관된 객체를 조회한다.

반면 DB에서는 외래 키를 사용해서 연관된 테이블을 조회한다.

참조와 외래 키의 불일치는 객체지향을 포기하게 만들 정도로 극복하기 어렵다.

객체는 외래 키가 필요 없고, DB는 참조가 필요 없기 때문에 개발자가 중간에서 변환의 역할을 해야 한다.

 

이러한 생산성 떨어지는 문제를 JPA에서 해결한다.

 

JPA는 참조를 외래 키로 변환해서 DB로 전달한다. 뿐만 아니라 외래 키를 참조로 변환하는 일도 처리해준다.

 

 

정리

JPA는 개발자가 SQL을 직접 작성하지 않게 해 준다. 이로 인해 개발자는 더 생산성이 높고 유지보수가 쉬워지는 개발을 진행할 수 있다.

또한, 객체와 DB사이의 패러다임 차이를 극복하기 위한 개발자의 수고를 덜어준다.

 

 

반응형