ORM이란? 앱이 데이터베이스와 대화하는 방법을 쉽게 설명
ORM(객체-관계 매퍼)은 앱과 관계형 데이터베이스 사이에 놓이는 코드 라이브러리입니다. 개발자는 데이터베이스 쿼리를 SQL로 작성하는 대신, db.booking.create(...) 또는 Booking.find(...)처럼 프로그래밍 언어의 일반 객체로 데이터를 다룹니다. ORM은 이를 SQL로 바꿔 실행하고, 결과를 다시 객체로 되돌려 줍니다. 대표적인 예로 TypeScript용 Prisma와 Drizzle, Python용 SQLAlchemy, C#용 Entity Framework Core가 있습니다.
AI로 만들 때 ORM이 중요한 이유
AI 앱 빌더가 앱에 데이터베이스를 추가하면, 보통 이를 다루기 위해 ORM도 함께 추가합니다. ORM 코드를 직접 작성할 필요는 없지만, 그것이 있다는 사실을 알아 두면 도움이 됩니다.
- 데이터 모델이 코드 안에 있습니다. ORM의 스키마 파일은 앱 데이터를 가장 명확하게 설명하는 자료인 경우가 많습니다. 어떤 테이블이 있는지, 각 테이블에 어떤 필드가 있는지, 서로 어떻게 연결되는지를 보여 줍니다. 이 파일을 읽으면 AI가 앱을 제대로 이해했는지 빠르게 확인할 수 있습니다.
- 데이터베이스 변경은 마이그레이션을 거칩니다. "고객에게 전화번호 필드를 추가해 줘"라고 요청하면 AI는 모델을 수정하고 마이그레이션을 만듭니다. 이 과정을 알면 변경 사항이 왜 라이브에서 동작하기 전에 적용되어야 하는지 이해하기 쉽습니다.
- 기본적으로 안전합니다. ORM은 쿼리를 SQL 인젝션을 막는 방식으로 만듭니다. SQL 인젝션은 쿼리를 직접 조립하는 앱에서 가장 흔한 보안 취약점 중 하나입니다.
- 속도 함정이 있습니다. ORM을 쓰면 한 번이면 될 쿼리를 작은 쿼리 수백 개로 나눠 실행하기 쉽습니다. AI로 만든 앱의 페이지가 느려지는 흔한 원인입니다.
일상의 비유
영어만 할 줄 아는 영국인 여행자와 일본어만 할 줄 아는 가게 주인이 통역사를 통해 대화한다고 상상해 보세요.
여행자(앱)가 "가장 저렴한 차 세 가지 주세요"라고 말합니다. 통역사(ORM)는 이를 정확한 일본어(SQL)로 바꿔 가게 주인(데이터베이스)에게 전하고, 답을 다시 영어로 옮겨 줍니다.
여행자는 일본어를 배울 필요가 없고, 대부분의 대화에서는 그것으로 충분합니다. 하지만 복잡한 협상에서는 직접 말하는 편이 나을 수 있습니다. 그래서 대부분의 ORM은 필요할 때 개발자가 원시 SQL을 작성할 수 있도록 합니다.
ORM은 어떻게 동작하나요
Postgres 같은 데이터베이스는 데이터를 행과 열로 이루어진 테이블에 저장하며, SQL로 조회합니다. 프로그래밍 언어는 객체로 작업합니다. 예를 들어 date, customer, status를 가진 예약 하나가 객체입니다. ORM은 이 둘을 서로 연결합니다.
| 데이터베이스 쪽 | ORM / 코드 쪽 |
|---|---|
테이블 bookings | 모델 Booking |
| 행 | 예약 객체 하나 |
열 starts_at | 필드 startsAt |
외래 키 customer_id | 관계 booking.customer |
SELECT, INSERT, UPDATE, DELETE | find, create, update, delete 메서드 |
이 네 가지 작업은 거의 모든 앱의 바탕이 되는 CRUD 작업입니다.
실제 예시: Prisma로 예약 처리하기
다음은 미용실 예약 앱을 단순화한 Prisma 스키마입니다. 두 테이블과 그 연결 관계를 설명합니다.
model Customer {
id Int @id @default(autoincrement())
name String
email String @unique
bookings Booking[]
}
model Booking {
id Int @id @default(autoincrement())
startsAt DateTime
service String
customer Customer @relation(fields: [customerId], references: [id])
customerId Int
}
쉬운 말로 풀면 이렇습니다. 고객은 이름, 고유한 이메일, 여러 개의 예약을 가질 수 있습니다. 각 예약은 시작 시간과 서비스 종류를 가지며, 한 명의 고객에게 속합니다.
이제 앱이 "Lena의 다가오는 예약을 가까운 순서대로 보여 줘"라는 요청을 처리해야 한다고 해 봅시다.
const bookings = await db.booking.findMany({
where: {
customer: { email: "lena@example.com" },
startsAt: { gte: new Date() },
},
orderBy: { startsAt: "asc" },
});
ORM은 이것을 대략 다음과 같은 SQL로 바꿔 실행하고, 예약 객체 목록을 돌려줍니다.
SELECT b.* FROM "Booking" b
JOIN "Customer" c ON c.id = b."customerId"
WHERE c.email = $1 AND b."startsAt" >= $2
ORDER BY b."startsAt" ASC;
$1과 $2는 자리 표시자입니다. 이메일과 날짜가 쿼리 텍스트와 별도로 전달되며, 이것이 SQL 인젝션을 막습니다.
마이그레이션
이어서 "고객이 전화번호를 추가할 수 있게 해 줘"라고 요청하면, AI는 Customer 모델에 phone String?를 추가하고 마이그레이션을 생성합니다. 여기서 ?는 선택 입력이라는 뜻입니다. 마이그레이션은 ALTER TABLE "Customer" ADD COLUMN "phone" TEXT;처럼 작은 SQL 스크립트입니다. 마이그레이션은 순서대로 저장되므로, 모든 데이터베이스 사본을 같은 구조로 맞출 수 있고 변경 이력도 남습니다.
언어별 대표 ORM
| 언어 | 대표 ORM |
|---|---|
| TypeScript / JavaScript | Prisma, Drizzle, TypeORM, Sequelize |
| Python | SQLAlchemy, Django ORM, SQLModel |
| C# | Entity Framework Core |
| Java | Hibernate (대개 Spring Data JPA와 함께) |
| Ruby | Active Record |
ORM마다 SQL을 얼마나 숨기는지가 다릅니다. Prisma는 자체 스키마 언어와 쿼리 방식을 사용하고, Drizzle은 타입 안정성을 유지하면서도 SQL의 형태에 가깝게 유지합니다. Knex나 TypeScript용 Kysely 같은 가벼운 도구인 쿼리 빌더는 행을 완전한 객체로 매핑하지 않고도 코드에서 SQL을 조립하도록 도와줍니다.
핵심 용어
| 용어 | 뜻 |
|---|---|
| 모델 | 테이블과 그 필드를 코드로 정의한 것입니다. |
| 스키마 | 모든 테이블, 필드, 관계를 전체적으로 설명한 것입니다. |
| 관계 | 모델 사이의 연결입니다. 예를 들어 예약이 고객에게 속하는 관계입니다. |
| 마이그레이션 | 데이터베이스 구조를 바꾸는 버전이 붙은 스크립트입니다. |
| 쿼리 빌더 | 전체 객체 매핑 없이 코드에서 SQL을 만드는 가벼운 도구입니다. |
| N+1 문제 | 목록을 한 번 조회한 뒤 항목마다 쿼리를 한 번씩 더 실행하는 문제입니다. 규모가 커지면 느려집니다. |
| 원시 SQL | 직접 작성한 SQL입니다. ORM이 방해가 될 때 사용합니다. |
흔한 오해
- "ORM은 데이터베이스다." 아닙니다. ORM은 번역기입니다. 여전히 Postgres 같은 데이터베이스가 필요합니다.
- "ORM을 쓰면 데이터를 이해할 필요가 없다." 잘못 설계된 테이블은 ORM을 써도 똑같이 불편합니다. 좋은 데이터 모델이 도구보다 더 중요합니다.
- "ORM은 항상 느리다." 일상적인 쿼리는 괜찮습니다. 느려지는 원인은 대개 N+1 쿼리나 인덱스 누락 같은 패턴이며, 이는 고칠 수 있습니다.
- "ORM을 쓰면 SQL 인젝션 위험은 영원히 없다." 표준 ORM 메서드는 안전하지만, 사용자 입력을 그대로 붙여 넣은 원시 SQL은 여전히 위험합니다.
- "스키마 파일을 수정하면 실제 데이터베이스가 바뀐다." 마이그레이션이 만들어져 그 데이터베이스에 적용된 뒤에야 바뀝니다.
AI 빌더에게 요청할 내용
- "이 프로젝트의 데이터베이스 스키마를 보여 주고, 각 테이블과 관계를 쉬운 말로 설명해 줘."
- "이 프로젝트는 어떤 ORM을 쓰고, 마이그레이션은 어디에 있나요?"
- "테이블을 바꾸기 전에 기존 데이터에 영향이 있는지 알려 줘. 데이터가 있는 열은 허락을 받기 전까지 절대 삭제하지 마."
- "가장 느린 페이지에서 N+1 쿼리를 찾아보고, 필터링이나 정렬이 일어나는 곳에 인덱스를 추가해 줘."
- "사용자 입력을 사용하는 모든 쿼리가 ORM을 거치거나 파라미터를 사용하도록 해 줘. 문자열로 SQL을 조립하면 안 됩니다."
Mythex의 ORM
앱이 데이터를 저장해야 하면 Mythex는 프로젝트에 전용 Postgres 데이터베이스를 추가합니다. 채팅에서 요청하거나 /database 명령어를 사용하면 됩니다. 연결 정보는 작업 공간과 게시된 앱에 주입됩니다(Database). 그다음 에이전트가 데이터를 읽고 저장하는 코드를 작성합니다. "Prisma를 쓰세요", "SQLAlchemy를 쓰세요", "ORM 없이 순수 SQL을 쓰세요"처럼 원하는 방식이 있다면 채팅에서 알려 주세요. 구조가 궁금할 때는 스키마를 설명해 달라고 요청하면 되고, 코드 편집기에서 파일을 직접 읽을 수도 있습니다. 더 넓은 관점은 데이터베이스란 무엇인가와 SQL vs NoSQL을 참고하세요.
자주 묻는 질문
ORM을 쉽게 말하면 무엇인가요?
ORM(객체-관계 매퍼)은 앱과 관계형 데이터베이스 사이에 놓이는 코드 라이브러리입니다. 개발자는 프로그래밍 언어의 일반 객체로 데이터를 읽고 저장할 수 있으며, 실제 SQL 쿼리는 ORM이 뒤에서 대신 작성합니다.
ORM의 대표적인 예는 무엇인가요?
대표적인 ORM으로는 TypeScript와 JavaScript용 Prisma와 Drizzle, Python용 SQLAlchemy와 Django ORM, C#용 Entity Framework Core, Java용 Hibernate, Ruby on Rails의 Active Record가 있습니다.
ORM이 SQL을 직접 쓰는 것보다 나은가요?
작업에 따라 다릅니다. ORM을 쓰면 일상적인 읽기와 쓰기를 더 빠르게 작성할 수 있고, 더 안전하며 변경하기도 쉽습니다. 반면 복잡한 보고서나 성능이 중요한 쿼리에는 직접 작성한 SQL이 완전한 제어를 제공합니다. 대부분의 ORM은 필요할 때 원시 SQL로 내려갈 수 있게 해 줍니다.
마이그레이션이란 무엇인가요?
마이그레이션은 테이블이나 열을 추가하는 것처럼 데이터베이스 구조를 바꾸는 작고 버전이 붙은 스크립트입니다. ORM은 데이터 모델을 바탕으로 마이그레이션을 생성할 수 있어서, 노트북, 테스트 환경, 운영 환경의 데이터베이스가 모두 같은 구조를 갖게 됩니다.