Entity&Column

장현욱(Artlogy)·2022년 11월 25일
0

TypeORM

목록 보기
4/5
post-thumbnail

앤티티(Entity)


앤티티는 데이터베이스 테이블(MongoDB의 경우 Collection)에 매핑되는 클래스이다.
새 클래스를 정의하여 앤티티를 만들고자 하면 다음과 같이 작성 할 수 있다.

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    firstName: string

    @Column()
    lastName: string

    @Column()
    isActive: boolean
}

그러면 다음과 같이 데이터베이스에 테이블이 생성된다.

+-------------+--------------+----------------------------+
|                          user                           |
+-------------+--------------+----------------------------+
| id          | int(11)      | PRIMARY KEY AUTO_INCREMENT |
| firstName   | varchar(255) |                            |
| lastName    | varchar(255) |                            |
| isActive    | boolean      |                            |
+-------------+--------------+----------------------------+

각 엔티티는 데이터 소스 옵션에 등록되어 있어야한다.

import { DataSource } from "typeorm"
import { User } from "./entity/User"

const myDataSource = new DataSource({
    type: "mysql",
    host: "localhost",
    port: 3306,
    username: "test",
    password: "test",
    database: "test",
  //요기에 등록해줘야 한다.
    entities: [User],
  //entities: ["entity/*.js"], 이런식으로 경로 exp를 사용 할 수도있다.
})

엔티티 상속


엔티티는 상속하여 코드의 중복을 줄일 수 있다.

export abstract class Content {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    title: string

    @Column()
    description: string
}
@Entity()
export class Photo extends Content {
    @Column()
    size: string
}

@Entity()
export class Question extends Content {
    @Column()
    answersCount: number
}

@Entity()
export class Post extends Content {
    @Column()
    viewCount: number
}

포함 앤티티(Embedded Entities)

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: string

    @Column()
    firstName: string

    @Column()
    lastName: string

    @Column()
    isActive: boolean
}

firstName, lastName은 같은 name속성안의 요소일 뿐이니 하나로 합치는게 좋을것이다.
이럴 때 쓸 수 있는것이 embedded 기법이다.

import { Column } from "typeorm"

export class Name {
    @Column()
    first: string

    @Column()
    last: string
}
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
import { Name } from "./Name"

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: string

    @Column(() => Name)
    name: Name

    @Column()
    isActive: boolean
}

트리 앤티티(Tree Entities)

typeorm에서 트리구조는 인접목록(Adjacency list),중첩세트(Nested set), 경로열거(Materialized Path) ,클로저 테이블(Closure Table)패턴을 지원한다.


인접 목록(Adjacency List)

인접 목록은 계층구조를 만든다고 했을때 생각 할 수 있는 가장 간단한 모델이다.
데이터 테이블에 부모키가 존재하는 식으로 상위 부모요소를 참조하여 계층구조를 만드는 식이다.

import {
    Entity,
    Column,
    PrimaryGeneratedColumn,
    ManyToOne,
    OneToMany,
} from "typeorm"

@Entity()
export class Category {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    name: string

    @Column()
    description: string

    @ManyToOne((type) => Category, (category) => category.children)
    parent: Category

    @OneToMany((type) => Category, (category) => category.parent)
    children: Category[]
}]

중첩 세트(Nested set)

중첩 셋은 left, right필드로 레코드가 포함되는 범위를 지정하는 방식이다.
루트의 경우는 0에서 N까지 존재하겠지만, 그 밑에 컴퓨터계층은 0(L)~17(R) 그 밑에 그래픽 카드는 0(L)~5(R) 그 밑에 제조사는 각각 0(지포스), 1(암드)... 이런식이다.

import {
    Entity,
    Tree,
    Column,
    PrimaryGeneratedColumn,
    TreeChildren,
    TreeParent,
    TreeLevelColumn,
} from "typeorm"

@Entity()
@Tree("nested-set")
export class Category {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    name: string

    @TreeChildren()
    children: Category[]

    @TreeParent()
    parent: Category
}

경로 열거 (Path enumeration)

웹페이지 주소와 같은 경로정보로 계층데이터를 저장하는 전략이다. Breadcrumb처럼 기본키로 경로를 만들어서 데이터 베이스에 저장한다.
/1/2/6/7/8 이런식으로 계층경로가 저장된다. ( 정말 파격적이고 가시성있는 방식이다. )

import {
    Entity,
    Tree,
    Column,
    PrimaryGeneratedColumn,
    TreeChildren,
    TreeParent,
    TreeLevelColumn,
} from "typeorm"

@Entity()
@Tree("materialized-path")
export class Category {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    name: string

    @TreeChildren()
    children: Category[]

    @TreeParent()
    parent: Category
}

클로저 테이블(Closure Table)

클로저 테이블은 부모 자식 간의 관계를 특별한 방식으로 별도의 테이블에 저장한다. 읽기/쓰기에 모두 효율적인 방법이기에 권장되는 트리구조이다.

import {
    Entity,
    Tree,
    Column,
    PrimaryGeneratedColumn,
    TreeChildren,
    TreeParent,
    TreeLevelColumn,
} from "typeorm"

@Entity()
@Tree("closure-table")
export class Category {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    name: string

    @Column()
    description: string

    @TreeChildren()
    children: Category[]

    @TreeParent()
    parent: Category

    @TreeLevelColumn()
    level: number
}

클로저 테이블은 데이터를 저장하는 테이블과 계층구조 정보를 가진 테이블을 나누어 관리 한다.

열(Column)


데이터베이스 테이블은 열로 구성되므로 앤티티도 열로 구성되어야 한다.
열은 @Column으로 매핑한다.

기본 열


각 앤티티에는 기본 열이 하나 이상 있어야 한다. 기본열에는 다음과 같은 유형이 존재한다.

PrimaryColumn()

모든 타입의 값을 취하는 기본열을 생성한다. 열 타입을 지정하지 않으면 속성 유형에서 유추된다. 아래는 int로 저장하기 전에 수동으로 할당해야 하는 유형으로 ID를 생성한다.

import { Entity, PrimaryColumn } from "typeorm"

@Entity()
export class User {
    @PrimaryColumn()
    id: number
}

PrimaryGeneratedColumn()

값이 자동으로 생성되는 기본열을 생성한다. default는 int형이며(DB마다 다르긴함), 저장하기 전에 값을 수동으로 할당할 필요는 없다.

import { Entity, PrimaryGeneratedColumn } from "typeorm"

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number;
  
  // 고유 문자열 ID인 UUID도 만들 수 있다. 당연히 값이 자동으로 할당된다.
  //@PrimaryGeneratedColumn("uuid")
  //id: string;
}

특수 열


  • @CreateDateColumn: 데이터 삽입 날짜로 자동 설정되는 특수 열이다.
  • @UpdateDateColumn: 데이터가 수정 날짜로 자동 설정되는 특수 열이다.
  • @DeleteDateColumn: 데이터가 삭제 날짜로 자동 설정되는 특수 열이다. (soft delete)
  • @VersionColumnsave : 데이터가 호출 될 때 버전값이 자동 설정되는 특수 열이다.

공간 열

DB마다 지원되는 이름은 다르지만 MySQL을 기준으로 공간열(Spatial column) 또한 지원한다.
공간 열은 말그대로 공간정보, 도형정보등 좌표값을 가지는 데이터를 저장하는 특수한 열이다.
예를 들어 고객의 위치정보를 바탕으로 가장 가까운 상점을 찾는경우에 일반 열로 연산을 해도 되지만 지원되는 공간열을 사용하면 더 간단하게 구현 할 수 있을것이다.


const origin = {
    type: "Point",
    coordinates: [0, 0],
}

await dataSource.manager
    .createQueryBuilder(Thing, "thing")
    // origin을 SRID와 일치 하는 도형으로 변환함, 두좌표 사이의 거리를 구함
    .where(
        "ST_Distance(geom, ST_SetSRID(ST_GeomFromGeoJSON(:origin), ST_SRID(geom))) > 0",
    )
    .orderBy({
        "ST_Distance(geom, ST_SetSRID(ST_GeomFromGeoJSON(:origin), ST_SRID(geom)))":
            {
                order: "ASC",
            },
    })
    .setParameters({
        // stringify GeoJSON
        origin: JSON.stringify(origin),
    })
    .getMany()

await dataSource.manager
    .createQueryBuilder(Thing, "thing")
    // 지오메트리 결과를 GeoJSON으로 변환하여 반환 ( 역 직열화가 필요함 )
    .select("ST_AsGeoJSON(ST_Buffer(geom, 0.1))::json geom")
    .from("thing")
    .getMany()

열유형


typeorm은 일반적인 유형을 모두 지원한다.

@Column("int")
@Column({ type: "int" })
@Column("varchar", { length: 200 })
// width는 타입의 자리수를 결정한다. (200자리수의 int를 쓰고 남는 건 0으로 채움 = zerofill)
@Column({ type: "int", width: 200 })

Enum

export enum UserRole {
    ADMIN = "admin",
    EDITOR = "editor",
    GHOST = "ghost",
}

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number

    @Column({
        type: "enum",
        enum: UserRole,
        default: UserRole.GHOST,
    })
    role: UserRole
}

Set

enum이랑 똑같음

export enum UserRole {
    ADMIN = "admin",
    EDITOR = "editor",
    GHOST = "ghost",
}

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number

    @Column({
        type: "set",
        enum: UserRole,
        default: [UserRole.GHOST, UserRole.EDITOR],
    })
    roles: UserRole[]
}

simple-array

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number

    @Column("simple-array")
    names: string[]
}
const user = new User()
user.names = ["Alexander", "Alex", "Sasha", "Shurik"]

simple-json

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number

    @Column("simple-json")
    profile: { name: string; nickname: string }
}
const user = new User()
user.profile = { name: "John", nickname: "Malkovich" }

{"name":"John","nickname":"Malkovich"} 값으로 저장된다. 때문에 데이터를 로드할 때 JSON.parse로 다시 객체타입으로 변환해주자.

Generated

@Entity()
export class User {
    @PrimaryColumn()
    id: number

    @Column()
    @Generated("uuid")
    uuid: string
}

uuid값이 자동으로 생성되서 DB에 저장된다.

열옵션

열 옵션은 앤티티 열에 대한 추가 옵션을 정의한다.

@Column({
    type: "varchar",
    length: 150,
    unique: true,
    // ...
})
name: string;

ColumnOptions

  • type : ColumnType 해당 열에 타입을 지정한다.
  • name : string 열의 이름을 지정한다.
  • length: number 열 유형의 길이 -ex: varchar(45)
  • width : number 열 유형의 표시 너비 -ex: int(11)
  • onUpdate : string ON UPDATE 트리거 열의 값이 변할때 실행되는 콜백
  • nullable : boolean NULL허용 여부를 정한다. 기본값은 false
  • update : boolean 값 업데이트 가능여부를 설정함 (상수화) 기본값 true
  • insert : boolean 개체를 처음 삽입 할 때 초기 데이터를 설정해야 되는지 여부 기본값 true
  • select : boolean 쿼리를 만들 때 기본적으로 이 열을 숨길지 여부 false로 하면 숨겨진다.
  • default : 열의 default값을 정함
  • primary : boolean 기본키 열로 만들지 여부 기본값 false
  • unique : boolean 유니크 열로 만들지 여부 기본값 false
  • commnet : string 열에 대한 코멘트(주석)
  • precision : number 10진수 열에 최대 자릿수를 설정함
  • scale : number 10진수 열에 소숫점 오른쪽 자리의 자리수를 설정함
  • zerofill : boolean Zerofill를 설정함
  • unsigned : boolean UNSIGNED를 설정함 (음수제거)
  • charset : string 열의 문자집합을 정의함 -ex:UTF-8
  • collation : string 열 데이터의 정렬을 정의 함 -ex:'ASC', 'DESC'
  • enum : string[] | AnyEnum 허용되는 값의 배열이나 enum클래스를 지정함
  • enumName : string 사용된 enum의 이름을 정의함
  • asExpression: string Generated 열의 표현식을 정의함
  • generatedType : "VIRTUAL" | "STORED" 생성 열의 유형을 정의함
  • array : boolean 배열 열 타입 정의
  • transformer : { from(value: DatabaseType): EntityType, to(value: EntityType): DatabaseType } 데이터를 임의로 변환 할 때 쓰임

0개의 댓글