🗄️ TypeORM3. Repository & DataMapper📖 개요

03 — Repository & DataMapper

질문: TypeORM은 Repository라는 한 겹을 두고, 그 Repository의 save() Hibernate와 다르게 동작하는가? 한 줄 답: Repository는 엔티티가 자기 영속성을 모르게 만드는 경계이고, 그래서 save()는 dirty checking 없이 매번 명시적으로 호출되는 upsert다.

이전 챕터(02 — Relations)가 엔티티들이 어떻게 묶이는가를 다뤘다면, 이번 챕터는 그 엔티티가 어떻게 DB와 만나는가다. 한 줄의 userRepository.save(user)가 SQL 두세 개로 풀려나가는 길을 6단계로 추적한다.


챕터 지도


읽는 순서

순서문서누구에게
101-datamapper-vs-activerecord”왜 두 가지 스타일이 있지?” 묻는 사람 — Fowler의 두 패턴부터
202-repository-api매일 쓰는 find/save정확한 의미를 다지고 싶은 사람
303-find-optionswhere/relations/In/Like 등 옵션을 한 번에 정리하고 싶은 사람
404-save-semantics”save가 왜 이렇게 느리지?”를 한 번이라도 의심해본 사람
505-custom-repositoryNestJS·DDD 진영에서 도메인 메서드를 어디 둘지 고민하는 사람
606-active-record-modeBaseEntity를 본 적 있고 “이거 써도 되나?” 망설이는 사람

추천 동선: 1·4를 먼저 읽으면 멘탈 모델이 잡힌다. 2·3은 레퍼런스로, 5·6은 실전 단계에서.


6개 문서 한 줄 요약

#문서한 줄 답
01DataMapper vs ActiveRecordDataMapper는 엔티티가 영속성을 모르게 분리하고, ActiveRecord는 엔티티가 영속성을 안다. TypeORM은 둘 다 지원한다 — 그래서 함정이 많다.
02Repository APIfind/findOne은 SELECT, save는 upsert, insert는 순수 INSERT, delete는 hard delete, softDelete는 컬럼 한 칸으로 죽이기.
03Find 옵션where·relations·order·take·skip·select·cache·withDeleted 여덟 키, In·Not·Like·Between·IsNull 다섯 operator.
04save 의미save()upsert이고 항상 SELECT를 먼저 한다 — 그래서 insert()보다 느리다. dirty checking은 없다.
05Custom RepositoryRepository.extend() 또는 클래스 상속으로 도메인 메서드를 한곳에. NestJS는 @InjectRepository로 주입.
06ActiveRecord 모드BaseEntity 상속 시 user.save()가 가능 — 짧지만 테스트·의존성·DataSource 다중화에 약하다.

왜 이 챕터가 중요한가

TypeORM의 거의 모든 예상치 못한 동작은 Repository 레이어에서 발생한다.

흔한 의문어느 문서가 답하는가
”save가 왜 SELECT부터 하지?“04 — save는 upsert, 존재 여부를 알아야 한다
”방금 update했는데 다른 컬럼이 안 바뀌네?02·04 — update는 dirty가 아니라 주어진 컬럼만 쓴다
”관계를 같이 저장하고 싶은데 안 되네?“02 — cascade 옵션이 필요
”soft delete한 레코드를 다시 찾고 싶은데?“03 — withDeleted: true
”리포지토리에 도메인 메서드 어디 둬?“05 — Custom Repository 패턴
”user.save() 되던데 그럼 그게 표준 아니야?“06 — ActiveRecord 모드의 함정

이 답들이 추측이 아니라 TypeORM 소스에서 나온다는 것을 보여주는 게 이 챕터의 목표다.


Why — 왜 Repository라는 한 겹을 두는가

답은 Martin Fowler의 1996년 책 Analysis Patterns까지 거슬러 올라간다.

  • ActiveRecord: 엔티티 클래스가 자기 save·delete를 안다. 짧고 빠르다. Rails·Django·Eloquent.
  • DataMapper: 엔티티는 POJO, 영속성은 Repository가 책임. 길지만 분리된다. Hibernate·TypeORM·MikroORM.

TypeORM은 둘 다 지원하지만 — 공식 권장은 DataMapper다. 이 챕터는 그 권장의 이유대가를 묻는다.


다음 챕터로

Repository의 find 옵션은 대부분의 쿼리를 표현한다. 하지만 서브쿼리·복잡한 join·raw SQL이 필요한 순간이 온다 — 그때가 04장의 출발점이다.

다음: 04 — QueryBuilder — find가 풀 수 없을 때 SQL의 손잡이를 다시 손에 쥐는 길.