메인 μ½˜ν…μΈ λ‘œ κ±΄λ„ˆλ›°κΈ°
findByIdλŠ” ID둜 단일 λ ˆμ½”λ“œλ₯Ό μ‘°νšŒν•˜λŠ” λ©”μ„œλ“œμž…λ‹ˆλ‹€. μ§€μ •λœ μ„œλΈŒμ…‹μœΌλ‘œ 데이터λ₯Ό λ‘œλ“œν•˜λ©°, λ ˆμ½”λ“œκ°€ μ—†μœΌλ©΄ NotFoundException을 λ°œμƒμ‹œν‚΅λ‹ˆλ‹€.
findByIdλŠ” BaseModelClass에 μ •μ˜λ˜μ–΄ μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€. Entityλ₯Ό μƒμ„±ν•˜λ©΄ Syncerκ°€ 각 Model ν΄λž˜μŠ€μ— μžλ™μœΌλ‘œ μƒμ„±ν•˜λŠ” ν‘œμ€€ νŒ¨ν„΄μž…λ‹ˆλ‹€.

νƒ€μž… μ‹œκ·Έλ‹ˆμ²˜

async findById<T extends SubsetKey>(
  subset: T,
  id: number
): Promise<SubsetMapping[T]>

μžλ™ 생성 μ½”λ“œ

SonamuλŠ” Entityλ₯Ό 기반으둜 λ‹€μŒ μ½”λ“œλ₯Ό μžλ™μœΌλ‘œ μƒμ„±ν•©λ‹ˆλ‹€:
// src/application/user/user.model.ts (μžλ™ 생성)
class UserModelClass extends BaseModelClass {
  @api({ httpMethod: "GET", clients: ["axios", "tanstack-query"], resourceName: "User" })
  async findById<T extends UserSubsetKey>(
    subset: T,
    id: number
  ): Promise<UserSubsetMapping[T]> {
    const { rows } = await this.findMany(subset, {
      id,
      num: 1,
      page: 1,
    });
    if (!rows[0]) {
      throw new NotFoundException(`μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” User ID ${id}`);
    }

    return rows[0];
  }
}
λ‚΄λΆ€ λ™μž‘:
  1. findManyλ₯Ό ν˜ΈμΆœν•˜μ—¬ ID둜 쑰회
  2. num: 1, page: 1둜 λ‹¨κ±΄λ§Œ κ°€μ Έμ˜΄
  3. κ²°κ³Όκ°€ μ—†μœΌλ©΄ NotFoundException λ°œμƒ
  4. 첫 번째 λ ˆμ½”λ“œ λ°˜ν™˜

λ§€κ°œλ³€μˆ˜

subset

μ‘°νšŒν•  λ°μ΄ν„°μ˜ μ„œλΈŒμ…‹μ„ μ§€μ •ν•©λ‹ˆλ‹€. νƒ€μž…: SubsetKey (예: "A", "B", "C") μ„œλΈŒμ…‹μ€ Entity μ •μ˜μ—μ„œ μ„€μ •ν•œ 데이터 ν˜•νƒœλ₯Ό κ²°μ •ν•©λ‹ˆλ‹€. 각 μ„œλΈŒμ…‹μ€ λ‹€λ₯Έ ν•„λ“œμ™€ 관계λ₯Ό 포함할 수 μžˆμŠ΅λ‹ˆλ‹€.
// κΈ°λ³Έ μ •λ³΄λ§Œ 쑰회
const user = await UserModel.findById("A", 1);

// 관계 데이터 포함 쑰회
const user = await UserModel.findById("B", 1);

id

μ‘°νšŒν•  λ ˆμ½”λ“œμ˜ IDμž…λ‹ˆλ‹€. νƒ€μž…: number
const user = await UserModel.findById("A", 123);

λ°˜ν™˜κ°’

νƒ€μž…: Promise<SubsetMapping[T]> μ§€μ •λœ μ„œλΈŒμ…‹ νƒ€μž…μ˜ λ ˆμ½”λ“œλ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€. νƒ€μž…μ€ μ„œλΈŒμ…‹μ— 따라 μžλ™μœΌλ‘œ μΆ”λ‘ λ©λ‹ˆλ‹€.
// user의 νƒ€μž…μ€ μžλ™μœΌλ‘œ UserSubsetMapping["A"]둜 좔둠됨
const user = await UserModel.findById("A", 1);

console.log(user.id);      // number
console.log(user.email);   // string
console.log(user.name);    // string

μ˜ˆμ™Έ

NotFoundException

ID에 ν•΄λ‹Ήν•˜λŠ” λ ˆμ½”λ“œκ°€ μ—†μœΌλ©΄ NotFoundException이 λ°œμƒν•©λ‹ˆλ‹€.
try {
  const user = await UserModel.findById("A", 999);
} catch (error) {
  if (error instanceof NotFoundException) {
    console.error("μ‚¬μš©μžλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€");
  }
}
HTTP μƒνƒœ μ½”λ“œ: 404

κΈ°λ³Έ μ‚¬μš©λ²•

λ‹¨μˆœ 쑰회

import { UserModel } from "./user/user.model";

class UserService {
  async getUser(userId: number) {
    // ID둜 μ‚¬μš©μž 쑰회 (μ„œλΈŒμ…‹ A)
    const user = await UserModel.findById("A", userId);
    
    return {
      id: user.id,
      email: user.email,
      name: user.name
    };
  }
}

APIμ—μ„œ μ‚¬μš©

findByIdλŠ” μžλ™μœΌλ‘œ @api λ°μ½”λ ˆμ΄ν„°κ°€ μ μš©λ˜μ–΄ REST API둜 λ…ΈμΆœλ©λ‹ˆλ‹€:
// μžλ™ μƒμ„±λœ μ½”λ“œ
@api({ httpMethod: "GET", clients: ["axios", "tanstack-query"], resourceName: "User" })
async findById<T extends UserSubsetKey>(
  subset: T,
  id: number
): Promise<UserSubsetMapping[T]> {
  // ...
}
μƒμ„±λœ ν΄λΌμ΄μ–ΈνŠΈ μ½”λ“œ:
// services.generated.ts (μžλ™ 생성)
import { UserService } from "@/services/UserService";

// ID둜 μ‚¬μš©μž 쑰회
const user = await UserService.getUser("A", 123);

μ„œλΈŒμ…‹λ³„ μ‚¬μš©

μ„œλΈŒμ…‹ A (κΈ°λ³Έ)

const user = await UserModel.findById("A", 1);

// μ‚¬μš© κ°€λŠ₯ν•œ ν•„λ“œ
user.id;        // number
user.email;     // string
user.name;      // string
user.created_at;// Date

μ„œλΈŒμ…‹ B (관계 포함)

const user = await UserModel.findById("B", 1);

// 관계 데이터 μžλ™ λ‘œλ“œ
user.id;              // number
user.email;           // string
user.name;            // string
user.posts;           // Post[] (1:N 관계)
user.profile;         // Profile (1:1 관계)

μ„œλΈŒμ…‹ C (쀑첩 관계)

const user = await UserModel.findById("C", 1);

// 쀑첩 κ΄€κ³„κΉŒμ§€ λ‘œλ“œ
user.posts[0].comments;   // Comment[] (쀑첩 관계)
user.profile.images;      // Image[] (쀑첩 관계)

μ‹€μ „ μ˜ˆμ‹œ

import { UserModel } from "./user/user.model";
import { api } from "sonamu";

class ProfileFrame {
  @api({ httpMethod: "GET" })
  async getProfile(userId: number) {
    // ID둜 μ‚¬μš©μž 쑰회 (ν”„λ‘œν•„ 정보 포함)
    const user = await UserModel.findById("Profile", userId);
    
    return {
      id: user.id,
      email: user.email,
      name: user.name,
      avatar: user.profile?.avatar_url,
      bio: user.profile?.bio,
      posts_count: user.posts?.length ?? 0
    };
  }
}

findById vs findOne vs findMany

findById

  • ID둜 단일 λ ˆμ½”λ“œ 쑰회
  • λ ˆμ½”λ“œκ°€ μ—†μœΌλ©΄ μ˜ˆμ™Έ λ°œμƒ
  • κ°€μž₯ κ°„λ‹¨ν•˜κ³  빠름
  • μžλ™ 생성됨
// ID둜 쑰회 (μ—†μœΌλ©΄ μ˜ˆμ™Έ)
const user = await UserModel.findById("A", 1);

findOne

  • 쑰건으둜 단일 λ ˆμ½”λ“œ 쑰회
  • λ ˆμ½”λ“œκ°€ μ—†μœΌλ©΄ null λ°˜ν™˜
  • λ³΅μž‘ν•œ 쑰건 κ°€λŠ₯
  • 직접 κ΅¬ν˜„ ν•„μš” (선택적)
// 쑰건으둜 쑰회 (μ—†μœΌλ©΄ null)
const user = await UserModel.findOne("A", {
  email: "[email protected]"
});

findMany

  • 쑰건으둜 μ—¬λŸ¬ λ ˆμ½”λ“œ 쑰회
  • νŽ˜μ΄μ§€λ„€μ΄μ…˜ 지원
  • 총 개수 λ°˜ν™˜
  • μžλ™ 생성됨
// 리슀트 쑰회 (νŽ˜μ΄μ§€λ„€μ΄μ…˜)
const { rows, total } = await UserModel.findMany("A", {
  status: "active",
  num: 20,
  page: 1
});

νƒ€μž… μ•ˆμ •μ„±

μ„œλΈŒμ…‹μ— 따라 λ°˜ν™˜ νƒ€μž…μ΄ μžλ™μœΌλ‘œ μΆ”λ‘ λ©λ‹ˆλ‹€:
// μ„œλΈŒμ…‹ A: κΈ°λ³Έ ν•„λ“œλ§Œ
const userA = await UserModel.findById("A", 1);
userA.posts;  // ❌ νƒ€μž… μ—λŸ¬: Property 'posts' does not exist

// μ„œλΈŒμ…‹ B: posts 포함
const userB = await UserModel.findById("B", 1);
userB.posts;  // βœ… Post[]

ν΄λΌμ΄μ–ΈνŠΈ μ‚¬μš©

React (TanStack Query)

import { useQuery } from "@tanstack/react-query";
import { UserService } from "@/services/UserService";

function UserProfile({ userId }: { userId: number }) {
  const { data: user, isLoading, error } = useQuery({
    queryKey: ["user", userId],
    queryFn: () => UserService.getUser("A", userId)
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>User not found</div>;

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

Vue

import { UserService } from "@/services/UserService";

export default {
  data() {
    return {
      user: null,
      loading: false,
      error: null
    };
  },
  async mounted() {
    this.loading = true;
    try {
      this.user = await UserService.getUser("A", this.userId);
    } catch (error) {
      this.error = error.message;
    } finally {
      this.loading = false;
    }
  }
};

μ£Όμ˜μ‚¬ν•­

1. μ„œλΈŒμ…‹ 선택

ν•„μš”ν•œ λ°μ΄ν„°λ§Œ ν¬ν•¨ν•˜λŠ” μ„œλΈŒμ…‹μ„ μ„ νƒν•˜μ„Έμš”. λΆˆν•„μš”ν•œ 관계λ₯Ό λ‘œλ“œν•˜λ©΄ μ„±λŠ₯이 μ €ν•˜λ©λ‹ˆλ‹€.
// ❌ λ‚˜μœ 예: λͺ¨λ“  관계 λ‘œλ“œ
const user = await UserModel.findById("Full", 1);

// βœ… 쒋은 예: ν•„μš”ν•œ ν•„λ“œλ§Œ
const user = await UserModel.findById("A", 1);

2. μ˜ˆμ™Έ 처리

findByIdλŠ” λ ˆμ½”λ“œκ°€ μ—†μœΌλ©΄ μ˜ˆμ™Έλ₯Ό λ°œμƒμ‹œν‚΅λ‹ˆλ‹€. ν•„μš”ν•œ 경우 μ˜ˆμ™Έ 처리λ₯Ό μΆ”κ°€ν•˜μ„Έμš”.
// ❌ λ‚˜μœ 예: μ˜ˆμ™Έ 처리 μ—†μŒ
const user = await UserModel.findById("A", userId);  // 404 μ—λŸ¬ κ°€λŠ₯

// βœ… 쒋은 예: μ˜ˆμ™Έ 처리
try {
  const user = await UserModel.findById("A", userId);
} catch (error) {
  if (error instanceof NotFoundException) {
    // μ μ ˆν•œ 처리
  }
}

3. null κ°€λŠ₯μ„±

λ ˆμ½”λ“œ 쑴재 μ—¬λΆ€κ°€ λΆˆν™•μ‹€ν•˜λ©΄ findOne을 μ‚¬μš©ν•˜μ„Έμš” (직접 κ΅¬ν˜„ ν•„μš”).
// findById: λ ˆμ½”λ“œκ°€ λ°˜λ“œμ‹œ μ‘΄μž¬ν•΄μ•Ό 함
const user = await UserModel.findById("A", 1);

// findOne: λ ˆμ½”λ“œκ°€ 없을 수 있음 (직접 κ΅¬ν˜„ ν•„μš”)
const user = await UserModel.findOne("A", { email: "[email protected]" });
if (user === null) {
  // λ ˆμ½”λ“œ μ—†μŒ 처리
}

λ‹€μŒ 단계