typeorm 에서 trie 릴레이션 표현하기
가정된 상황 예시
- 공유 노트 어플리케이션
- 사용자는 n개의 노트를 가진다.
- 사용자는 n개의 노트를 공유 받을 수 있다.
- 또한 n개의 노트를 공유할 수 있다.
엔터티
- User
- Note
- SharedNote (trie mapping table )
관계 모델링
- User-Note 는 1:N 관계이다.
- 트라이 관계
- User-SharedNote 는 1:N관계 (A가 공유한)
- User-SharedNote 는 1:N관계 (B가 공유받은)
- Note-SharedNote 는 1:N관계 (C라는 노트를)
user.entity.ts
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
import { Note } from './note.entity';
import { SharedNote } from './sharedNote.entity';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@OneToMany(() => Note, (note) => note.owner)
notes: Note;
@OneToMany(() => SharedNote, (sharedNote) => sharedNote.target)
notesYouShare: SharedNote[];
@OneToMany(() => SharedNote, (sharedNote) => sharedNote.sender)
notesSharedWithYou: SharedNote[];
}
export type UserRelation =
| keyof Pick<User, 'notes' | 'notesSharedWithYou' | 'notesYouShare'>
| 'notesSharedWithYou.note'
| 'notesYouShare.note';
note.entity.ts
import {
Column,
Entity,
JoinColumn,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
import { SharedNote } from './sharedNote.entity';
import { User } from './user.entity';
@Entity()
export class Note {
@PrimaryGeneratedColumn()
id: number;
@Column()
text: string;
@Column()
ownerId: number;
@ManyToOne(() => User, (user) => user.notes)
@JoinColumn({ name: 'ownerId' })
owner: User;
@OneToMany(() => SharedNote, (sharedNote) => sharedNote.note)
shares: SharedNote;
}
sharedNote.entity.ts
import { Entity, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm';
import { Note } from './note.entity';
import { User } from './user.entity';
@Entity()
export class SharedNote {
@PrimaryColumn()
senderId: number;
@ManyToOne(() => User, (user) => user.notesSharedWithYou)
@JoinColumn({ name: 'senderId' })
sender: User;
@PrimaryColumn()
targetId: number;
@ManyToOne(() => User, (user) => user.notesYouShare)
@JoinColumn({ name: 'targetId' })
target: User;
@PrimaryColumn()
noteId: number;
@ManyToOne(() => Note, (note) => note.shares)
@JoinColumn({ name: 'noteId' })
note: Note;
}
CRUD
- ✔ 단일객체에서 CRUD
- 1:N 관계를 가지는 사용자와 notes 간에 CRUD
- 사용자 id 만으로 매핑이 되어야 한다.
- 사용자 객체를 이용해 매핑이 되어야 한다.
- N:M 관계를 가지는 사용자와 노트 공유자 간에 CURD
- 2명의 사용자 id 만으로 매핑이 되어야 한다.
- 2명의 사용자 객체를 이용해 매핑이 되어야 한다.
- sharedNote 객체 입장에서, 사용자를 1step join해야한다.
- 사용자 입장에서, 매핑테이블을 이용해 노트까지 2step join 해야한다.
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Like, Repository } from 'typeorm';
import { Note } from './entities/note.entity';
import { SharedNote } from './entities/sharedNote.entity';
import { User, UserRelation } from './entities/user.entity';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private readonly userRepo: Repository<User>,
@InjectRepository(Note)
private readonly noteRepo: Repository<Note>,
@InjectRepository(SharedNote)
private readonly sharedNoteRepo: Repository<SharedNote>,
) {
const test = async () => {
const dodo = await userRepo.findOne({
where: { username: Like('%do%') },
});
const nana = await userRepo.findOne({
where: { username: Like('%nana%') },
});
const dodosNote1 = await noteRepo.findOne({
where: { ownerId: nana.id, text: Like('%1%') },
});
const NotesNanaShare = await sharedNoteRepo.find({
where: { sender: nana },
relations: ['sender', 'target', 'note'],
});
const dodoUser = await userRepo.findOne({
where: { username: Like('%nana%') },
relations: [
'notes',
'notesSharedWithYou',
'notesSharedWithYou.note',
] as UserRelation[],
});
console.log(JSON.stringify(dodoUser, null, 4));
};
test();
}
}