๋ฉ”์ธ ์ฝ˜ํ…์ธ ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
getPuri๋Š” Puri ์ฟผ๋ฆฌ ๋นŒ๋”๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค. ์ง์ ‘ SQL ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜๊ฑฐ๋‚˜ UpsertBuilder๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

ํƒ€์ž… ์‹œ๊ทธ๋‹ˆ์ฒ˜

getPuri(which: DBPreset): PuriWrapper

๋งค๊ฐœ๋ณ€์ˆ˜

which

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ”„๋ฆฌ์…‹์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ํƒ€์ž…: DBPreset ("r" | "w")
  • "r": Read ์ „์šฉ (๋ณต์ œ DB, ์กฐํšŒ์šฉ)
  • "w": Write ๊ฐ€๋Šฅ (Primary DB, ์“ฐ๊ธฐ์šฉ)
// ์ฝ๊ธฐ ์ „์šฉ
const rdb = this.getPuri("r");

// ์“ฐ๊ธฐ ๊ฐ€๋Šฅ
const wdb = this.getPuri("w");
"r"๊ณผ "w"๋Š” ๋™์ผํ•œ DB๋ฅผ ๊ฐ€๋ฆฌํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์„ค์ •์— ๋”ฐ๋ผ ๋‹ค๋ฆ…๋‹ˆ๋‹ค.

๋ฐ˜ํ™˜๊ฐ’

ํƒ€์ž…: PuriWrapper Puri ์ฟผ๋ฆฌ ๋นŒ๋” ๋ž˜ํผ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
const wdb = this.getPuri("w");

// Puri ์ฟผ๋ฆฌ ๋นŒ๋” ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ
const users = await wdb.table("users").where("status", "active").select();

PuriWrapper ๋ฉ”์„œ๋“œ

table / from

ํ…Œ์ด๋ธ”์„ ์ง€์ •ํ•˜์—ฌ Puri ์ฟผ๋ฆฌ ๋นŒ๋”๋ฅผ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.
const wdb = this.getPuri("w");

// table() ์‚ฌ์šฉ
const users = await wdb.table("users")
  .where("status", "active")
  .select();

// from() ์‚ฌ์šฉ (๋™์ผ)
const posts = await wdb.from("posts")
  .where("published", true)
  .select();

transaction

ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.
const wdb = this.getPuri("w");

await wdb.transaction(async (trx) => {
  // ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ์ฟผ๋ฆฌ ์‹คํ–‰
  await trx.table("users").insert({ ... });
  await trx.table("posts").insert({ ... });
  
  // ๋ชจ๋‘ ์„ฑ๊ณตํ•˜๊ฑฐ๋‚˜ ๋ชจ๋‘ ์‹คํŒจ
});

UpsertBuilder ๋ฉ”์„œ๋“œ

ubRegister

UpsertBuilder์— ๋ ˆ์ฝ”๋“œ๋ฅผ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.
const wdb = this.getPuri("w");

wdb.ubRegister("users", {
  email: "[email protected]",
  name: "John"
});

ubUpsert

๋“ฑ๋ก๋œ ๋ ˆ์ฝ”๋“œ๋ฅผ Upsertํ•ฉ๋‹ˆ๋‹ค.
await wdb.transaction(async (trx) => {
  const ids = await trx.ubUpsert("users");
  return ids;
});

ubInsertOnly

๋“ฑ๋ก๋œ ๋ ˆ์ฝ”๋“œ๋ฅผ Insert๋งŒ ํ•ฉ๋‹ˆ๋‹ค (UPDATE ์—†์Œ).
await wdb.transaction(async (trx) => {
  const ids = await trx.ubInsertOnly("users");
  return ids;
});

ubUpdateBatch

๋“ฑ๋ก๋œ ๋ ˆ์ฝ”๋“œ๋ฅผ ๋ฐฐ์น˜ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
wdb.ubRegister("users", { id: 1, name: "Updated" });
wdb.ubRegister("users", { id: 2, name: "Updated 2" });

await wdb.ubUpdateBatch("users", {
  chunkSize: 500,
  where: "id"
});

raw

Raw SQL์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
const wdb = this.getPuri("w");

const result = await wdb.raw(`
  SELECT * FROM users WHERE status = ?
`, ["active"]);

๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

์ง์ ‘ ์ฟผ๋ฆฌ ์ž‘์„ฑ

import { BaseModelClass } from "sonamu";

class UserModelClass extends BaseModelClass {
  async getActiveUsers() {
    const rdb = this.getPuri("r");
    
    const users = await rdb.table("users")
      .where("status", "active")
      .orderBy("created_at", "desc")
      .limit(10)
      .select();
    
    return users;
  }
}

๋ณต์žกํ•œ ์ฟผ๋ฆฌ

async getUserStats() {
  const rdb = this.getPuri("r");
  
  const stats = await rdb.table("users")
    .leftJoin("posts", "users.id", "posts.user_id")
    .groupBy("users.id")
    .select({
      user_id: "users.id",
      user_name: "users.name",
      post_count: rdb.raw("COUNT(posts.id)")
    });
  
  return stats;
}

ํŠธ๋žœ์žญ์…˜ ์‚ฌ์šฉ

async transferPoints(fromUserId: number, toUserId: number, points: number) {
  const wdb = this.getPuri("w");
  
  await wdb.transaction(async (trx) => {
    // ํฌ์ธํŠธ ์ฐจ๊ฐ
    await trx.table("users")
      .where("id", fromUserId)
      .decrement("points", points);
    
    // ํฌ์ธํŠธ ์ฆ๊ฐ€
    await trx.table("users")
      .where("id", toUserId)
      .increment("points", points);
    
    // ํžˆ์Šคํ† ๋ฆฌ ๊ธฐ๋ก
    await trx.table("point_history").insert({
      from_user_id: fromUserId,
      to_user_id: toUserId,
      points,
      created_at: new Date()
    });
  });
}

ํŠธ๋žœ์žญ์…˜ ์ปจํ…์ŠคํŠธ

getPuri๋Š” ํŠธ๋žœ์žญ์…˜ ์ปจํ…์ŠคํŠธ๋ฅผ ์ž๋™์œผ๋กœ ์ธ์‹ํ•ฉ๋‹ˆ๋‹ค.
import { BaseModelClass, transactional, api } from "sonamu";

class UserModelClass extends BaseModelClass {
  @api({ httpMethod: "POST" })
  @transactional()
  async createUserWithProfile(params: {
    email: string;
    name: string;
    bio: string;
  }) {
    // @transactional ๋‚ด์—์„œ getPuri ํ˜ธ์ถœ
    const wdb = this.getPuri("w");
    
    // ์ž๋™์œผ๋กœ ํŠธ๋žœ์žญ์…˜ ์ปจํ…์ŠคํŠธ ์‚ฌ์šฉ
    const [user] = await wdb.table("users")
      .insert({
        email: params.email,
        name: params.name
      })
      .returning("*");
    
    await wdb.table("profiles")
      .insert({
        user_id: user.id,
        bio: params.bio
      });
    
    return user;
  }
}
@transactional ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด getPuri("w")๋Š” ์ž๋™์œผ๋กœ ํ•ด๋‹น ํŠธ๋žœ์žญ์…˜์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์‹ค์ „ ์˜ˆ์‹œ

import { BaseModelClass } from "sonamu";

class AnalyticsModelClass extends BaseModelClass {
  async getDailySales(startDate: Date, endDate: Date) {
    const rdb = this.getPuri("r");
    
    const sales = await rdb.table("orders")
      .whereBetween("created_at", [startDate, endDate])
      .groupBy(rdb.raw("DATE(created_at)"))
      .select({
        date: rdb.raw("DATE(created_at)"),
        total_orders: rdb.raw("COUNT(*)"),
        total_amount: rdb.raw("SUM(total_amount)"),
        avg_amount: rdb.raw("AVG(total_amount)")
      })
      .orderBy("date", "asc");
    
    return sales;
  }
  
  async getTopUsers(limit: number = 10) {
    const rdb = this.getPuri("r");
    
    const users = await rdb.table("users")
      .leftJoin("orders", "users.id", "orders.user_id")
      .groupBy("users.id")
      .select({
        user_id: "users.id",
        user_name: "users.name",
        order_count: rdb.raw("COUNT(orders.id)"),
        total_spent: rdb.raw("COALESCE(SUM(orders.total_amount), 0)")
      })
      .orderBy("total_spent", "desc")
      .limit(limit);
    
    return users;
  }
}

getPuri vs getDB

getPuri

Puri ์ฟผ๋ฆฌ ๋นŒ๋” + UpsertBuilder๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
const wdb = this.getPuri("w");

// Puri ์ฟผ๋ฆฌ ๋นŒ๋”
await wdb.table("users").where("id", 1).select();

// UpsertBuilder
wdb.ubRegister("users", { ... });
await wdb.ubUpsert("users");

getDB

์ˆœ์ˆ˜ Knex ์ธ์Šคํ„ด์Šค๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
const wdb = this.getDB("w");

// Knex ์ง์ ‘ ์‚ฌ์šฉ
await wdb("users").where("id", 1).select();
๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ getPuri๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. ํƒ€์ž… ์•ˆ์ •์„ฑ๊ณผ UpsertBuilder ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

ํŠธ๋žœ์žญ์…˜ ๊ฒฉ๋ฆฌ ์ˆ˜์ค€

const wdb = this.getPuri("w");

await wdb.transaction(
  async (trx) => {
    // ํŠธ๋žœ์žญ์…˜ ์ž‘์—…
  },
  {
    isolation: "serializable",  // ๊ฒฉ๋ฆฌ ์ˆ˜์ค€
    readOnly: false              // ์ฝ๊ธฐ ์ „์šฉ ์—ฌ๋ถ€
  }
);
๊ฒฉ๋ฆฌ ์ˆ˜์ค€:
  • "read uncommitted"
  • "read committed" (PostgreSQL ๊ธฐ๋ณธ)
  • "repeatable read" (MySQL ๊ธฐ๋ณธ)
  • "serializable"

ํƒ€์ž… ์•ˆ์ •์„ฑ

Puri๋Š” ์™„์ „ํ•œ ํƒ€์ž… ์•ˆ์ •์„ฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
const wdb = this.getPuri("w");

// โœ… ํƒ€์ž… ์•ˆ์ „
const users = await wdb.table("users")
  .where("status", "active")  // status ํ•„๋“œ ์ž๋™ ์™„์„ฑ
  .select();

users[0].email;  // โœ… ํƒ€์ž… ์ถ”๋ก ๋จ

// โŒ ํƒ€์ž… ์—๋Ÿฌ
await wdb.table("users")
  .where("unknown_field", "value");  // ์กด์žฌํ•˜์ง€ ์•Š๋Š” ํ•„๋“œ

์ฃผ์˜์‚ฌํ•ญ

1. ์ฝ๊ธฐ/์“ฐ๊ธฐ ๊ตฌ๋ถ„

์ฝ๊ธฐ ์ž‘์—…์€ "r", ์“ฐ๊ธฐ ์ž‘์—…์€ "w"๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
// โœ… ์˜ฌ๋ฐ”๋ฆ„: ์ฝ๊ธฐ๋Š” "r"
const rdb = this.getPuri("r");
const users = await rdb.table("users").select();

// โœ… ์˜ฌ๋ฐ”๋ฆ„: ์“ฐ๊ธฐ๋Š” "w"
const wdb = this.getPuri("w");
await wdb.table("users").insert({ ... });

2. ํŠธ๋žœ์žญ์…˜ ์ปจํ…์ŠคํŠธ

@transactional ๋‚ด์—์„œ๋Š” ํŠธ๋žœ์žญ์…˜์ด ์ž๋™์œผ๋กœ ๊ด€๋ฆฌ๋ฉ๋‹ˆ๋‹ค.
@transactional()
async method() {
  const wdb = this.getPuri("w");
  // wdb๋Š” ์ž๋™์œผ๋กœ ํŠธ๋žœ์žญ์…˜ ์‚ฌ์šฉ
}

3. UpsertBuilder๋Š” ํŠธ๋žœ์žญ์…˜ ํ•„์ˆ˜

// โŒ ์—๋Ÿฌ: ํŠธ๋žœ์žญ์…˜ ์—†์Œ
const wdb = this.getPuri("w");
wdb.ubRegister("users", { ... });
await wdb.ubUpsert("users");  // ์—๋Ÿฌ!

// โœ… ์˜ฌ๋ฐ”๋ฆ„: ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ
await wdb.transaction(async (trx) => {
  trx.ubRegister("users", { ... });
  await trx.ubUpsert("users");
});

์„ฑ๋Šฅ ์ตœ์ ํ™”

1. ์ธ๋ฑ์Šค ํ™œ์šฉ

// WHERE ์กฐ๊ฑด์— ์ธ๋ฑ์Šค ์‚ฌ์šฉ
const users = await rdb.table("users")
  .where("status", "active")  // status์— ์ธ๋ฑ์Šค ํ•„์š”
  .where("created_at", ">", startDate)  // created_at์— ์ธ๋ฑ์Šค ํ•„์š”
  .select();

2. SELECT ํ•„๋“œ ์ œํ•œ

// โŒ ๋ชจ๋“  ํ•„๋“œ ์กฐํšŒ
const users = await rdb.table("users").select();

// โœ… ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ
const users = await rdb.table("users")
  .select("id", "email", "name");

3. ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ

// ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ๋Š” ๋ฐฐ์น˜๋กœ ์ฒ˜๋ฆฌ
for (let i = 0; i < ids.length; i += 500) {
  const chunk = ids.slice(i, i + 500);
  await wdb.table("users").whereIn("id", chunk).update({ ... });
}

๋‹ค์Œ ๋‹จ๊ณ„