개발/DataBase

[Database] ORM(Object Relational Mapping) (feat. Typeorm)

훈배 2023. 3. 10. 02:13

ORM(Object Relational Mapping)이란?

ORM이란 Object Relational Mapping의 약자로 이름 그대로 객체(Object)관계형 데이터베이스(Relational Database)의 데이터를 자동으로 매핑(연결)해주는 것을 말합니다.
 

ORM(Object Relational Mapping)

그림처럼 ORM은 객체 모델관계형 모델 사이의 관계를 기술하는 도구인데요. 하지만 객체와 DB의 테이블은 개념과 속성이 달라 매핑을 할 때 모델 간 불일치가 존재합니다.
 

Object - Relation mismatching

패러다임 불일치

 

- 세분성(Granularity) 

  • 경우에 따라 DB의 테이블 수보다 더 많은 클래스의 모델이 생길 수 있습니다.

- 상속성(Inheritance) 

  • RDBMS는 객체 지향 프로그래밍의 특징인 상속의 개념이 없습니다.

- 일치(Identity)

  •  RDBMS는 기본키(Primary Key)를 이용하여 동일성을 정의합니다.
  • 프로그래밍 언어 단에서는 연산자나, 동일성 검사 메서드를 통해 동일성을 정의합니다. 

- 연관성(Associations)

  • 객체지향 언어는 방향성이 있는 객체의 참조를 사용하여 연관성을 나타냅니다.
  • 반면 RDBMS는 방향성이 없는 외래키(Foreign Key)를 이용하여 나타냅니다.
  • 즉, ORM을 사용하면 프로그램에서 양방향 관계가 필요한 경우 서로의 Reference를 가지고 연관을 두 번 정의해야 합니다.

- 탐색(Navigation)

  • 객체는 그래프 형태로 탐색합니다.(하나의 연결에서 다른 연결로 이동하며 탐색)
  • RDBMS에서는 SQL문을 최소화하고 JOIN을 통해 여러 엔티티를 로드하고 원하는 대상을 선택하는 방식으로 탐색합니다.

 
ORM은 통해 객체 간의 관계를 바탕으로 SQL을 자동으로 생성하여 불일치를 해결합니다. 이로써 객체를 통해 데이터 베이스를 간접적으로 다룰 수 있게 됩니다. 이렇게 데이터 베이스를 객체를 통해 조작하게 된 이유는 무엇일까요?
 

ORM(Object Relational Mapping) 장/단점

ORM 장점

- 직관적인 코드 (가독성) + 비지니스 로직 집중 가능 (생산성)

ORM을 이용하면 SQL Query 가 아닌 메서드로 데이터를 조작하게 됩니다. 이로써 프로그래머가 객체 모델로 프로그래밍하는 것에 더 집중할 수 있게 도와줍니다. 또한 각종 객체에 대한 코드를 별도로 작성하기 때문에 코드 가독성을 높여주며,  SQL의 절차적이고 순차적인 접근이 아닌 객체 지향적인 접근으로 생산성이 증가합니다.
 

- 재사용 및 유지보수 편리성 증가

ORM은 독립적으로 작성되었고 해당 객체들을 재활용할 수 있기 때문에 디자인 패턴을 견고하게 만드는 데 유리합니다.
 

- DBMS에 대한 종속성 저하

객체 간 관계를 바탕으로 SQL을 자동으로 생성하기 때문에 RDBMS의 데이터 구조와 프로그래밍 언어의 객체 모델 사이의 간격을 좁혀 ORM을 이용한 솔루션은 DB에 종속적이지 않을 수 있습니다. 또한 프로그래머는 Object에 집중하므로 DBMS를 다루는 큰 작업에도 비교적 적은 리스크와 시간만을 소요할 수 있습니다.
 

ORM 단점

- 완벽히 ORM만으로 서비스를 구현하는데 한계

이처럼 ORM이 편리하고 생산성을 높여주지만 설계를 신중히 해야 하는 점이 있습니다. 잘못 구현할 경우 일관성이 무너져 큰 문제를 발생시킬 수 있어서 프로젝트의 복잡성이 커질 경우 구현 난이도가 상승합니다.
 

Typeorm

Typescript와 함께 사용할 수 있는 ORM인 Typeorm으로 간단한 DB 구현을 끝으로 글을 마치겠습니다. 

import {Entity, Column, BaseEntity, PrimaryGeneratedColumn, JoinColumn, OneToOne} from "typeorm";

@Entity()
export default class User extends BaseEntity {
  @PrimaryGeneratedColumn()
  userid: number;

  @Column({length: 40})
  nickname: string;
  
  @OneToOne(() => UserConfig, (config) => config.user) //두 번째 인자는 관계 기준이 될 반대쪽 프로퍼티를 명시
  config: UserConfig;
}

위의 코드와 같이 userid와 nickname을 속성으로 가지는 User라는 엔티티를 만들었습니다. @OneToOne을 사용하여 UserConfig 엔티티와 1대 1 관계를 설정하였고 config는 UserConfig.user를 기준으로 UserConfig 엔티티를 참조하게 됩니다.
 
* 연관관계에는 

  • OneToOne 일대일
  • OneToMany 일대다
  • ManyToOne 다대일
  • ManyToMany 다대다 

가 있다.

export enum CommonYN {
  Y = "Y",
  N = "N",
}
import {CommonYN} from "@common/CommonConstants";
import {Entity, Column, BaseEntity, PrimaryColumn, JoinColumn, OneToOne} from "typeorm";
import User from "./User";

@Entity()
export default class UserConfig extends BaseEntity {
  @PrimaryColumn()
  userid: number;
  
  @Column({type: "enum", enum: CommonYN, default: CommonYN.N})
  adminyn: CommonYN;
  
  @OneToOne(() => User, (user) => user.userid) //두 번째 인자는 관계 기준이 될 반대쪽 프로퍼티를 명시
  @JoinColumn({name: "userid"})
  user: User;
}

위의 코드는 관리자 여부를 조회하는 UserConfig 엔티티를 나타내고 @OneToOne을 사용하여 User 엔티티와 1대 1 관계를 설정하고 user가 User.userid를 기준으로 User 엔티티를 참조하도록 하였습니다. 또한, @JoinColumn을 이용하여 User 엔티티의 기본키인 userid 속성을 UserConfig의 기본키(Primary Key)이자 외래키(Foreign Key)로 지정하였습니다. 참고로 외래키를 가진 쪽이 관계의 주인이 되므로 UserConfig가 관계의 주인이 됩니다.
 
위의 관계를 그림으로 나타내면 아래와 같습니다.

관계도

 

데이터 로드

또한 아래와 같이 relations 옵션을 사용하여 관계되어 있는 테이블의 정보를 가져올 수 있습니다. (typeorm에서는 아래와 같이 기능을 따로 뺄 수도 있지만 엔티티 안에서 디폴트 기능으로 사용할 수도 있다.)

function getUserInfo(userid: number) {
  return await User.findOne({
    where: {userid}, relations: {config: true},
  });
}

이렇게 하면 User 테이블의 정보뿐만 아니라 같은 userid를 공유하는 UserConfig의 정보도 가져올 수 있습니다.
 
 
오늘은 이렇게 ORM에 대해 알아보았는데요 ORM은 편리하고 생산성을 높일 수 있는 좋은 도구이지만 그만큼 적절한 환경에서 잘 사용하였을 때 빛을 발하는 도구인 것 같습니다. 
 
** 틀린 내용이 있을 시 지적해 주시면 감사하겠습니다. 
 

참조

https://typeorm.io
 

'개발 > DataBase' 카테고리의 다른 글

[Database] 트랜잭션(Transaction)  (2) 2023.05.27