getPuri๋ Puri ์ฟผ๋ฆฌ ๋น๋๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฉ์๋์
๋๋ค. ์ง์ SQL ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๊ฑฐ๋ UpsertBuilder๋ฅผ ์ฌ์ฉํ ๋ ํ์ํฉ๋๋ค.
ํ์
์๊ทธ๋์ฒ
getPuri ( which : DBPreset ): PuriWrapper
๋งค๊ฐ๋ณ์
๋ฐ์ดํฐ๋ฒ ์ด์ค ํ๋ฆฌ์
์ ์ง์ ํฉ๋๋ค.
ํ์
: 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 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")๋ ์๋์ผ๋ก ํด๋น ํธ๋์ญ์
์ ์ฌ์ฉํฉ๋๋ค.
์ค์ ์์
์ง๊ณ ์ฟผ๋ฆฌ
Raw SQL
๋ฐฐ์น ์์
๋ณต์กํ ํธ๋์ญ์
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 ;
}
}
import { BaseModelClass } from "sonamu" ;
class ReportModelClass extends BaseModelClass {
async getComplexReport () {
const rdb = this . getPuri ( "r" );
// Raw SQL๋ก ๋ณต์กํ ์ฟผ๋ฆฌ
const [ result ] = await rdb . raw ( `
WITH monthly_sales AS (
SELECT
DATE_FORMAT(created_at, '%Y-%m') as month,
COUNT(*) as order_count,
SUM(total_amount) as total
FROM orders
WHERE status = 'completed'
GROUP BY month
)
SELECT
month,
order_count,
total,
LAG(total) OVER (ORDER BY month) as prev_month_total,
(total - LAG(total) OVER (ORDER BY month)) / LAG(total) OVER (ORDER BY month) * 100 as growth_rate
FROM monthly_sales
ORDER BY month DESC
LIMIT 12
` );
return result ;
}
async searchFullText ( keyword : string ) {
const rdb = this . getPuri ( "r" );
// Full-text search
const posts = await rdb . raw ( `
SELECT
id,
title,
content,
MATCH(title, content) AGAINST(? IN BOOLEAN MODE) as relevance
FROM posts
WHERE MATCH(title, content) AGAINST(? IN BOOLEAN MODE)
ORDER BY relevance DESC
LIMIT 20
` , [ keyword , keyword ]);
return posts [ 0 ];
}
}
import { BaseModelClass } from "sonamu" ;
class BatchModelClass extends BaseModelClass {
async batchUpdateStatus (
userIds : number [],
status : string
) {
const wdb = this . getPuri ( "w" );
// 500๊ฐ์ฉ ๋ฐฐ์น ์ฒ๋ฆฌ
const chunkSize = 500 ;
let updated = 0 ;
for ( let i = 0 ; i < userIds . length ; i += chunkSize ) {
const chunk = userIds . slice ( i , i + chunkSize );
const count = await wdb . table ( "users" )
. whereIn ( "id" , chunk )
. update ({
status ,
updated_at: new Date ()
});
updated += count ;
}
return { updated };
}
async batchInsert ( users : Array <{ email : string ; name : string }>) {
const wdb = this . getPuri ( "w" );
return wdb . transaction ( async ( trx ) => {
// UpsertBuilder๋ก ๋ฐฐ์น ๋ฑ๋ก
users . forEach ( user => {
trx . ubRegister ( "users" , {
email: user . email ,
name: user . name ,
status: "active" ,
created_at: new Date ()
});
});
// ํ ๋ฒ์ upsert
const ids = await trx . ubUpsert ( "users" , {
chunkSize: 500
});
return { count: ids . length , ids };
});
}
}
import { BaseModelClass } from "sonamu" ;
class OrderModelClass extends BaseModelClass {
async processOrder ( orderId : number ) {
const wdb = this . getPuri ( "w" );
return wdb . transaction ( async ( trx ) => {
// 1. ์ฃผ๋ฌธ ์กฐํ
const [ order ] = await trx . table ( "orders" )
. where ( "id" , orderId )
. forUpdate () // ๋ฝ ๊ฑธ๊ธฐ
. select ();
if ( ! order ) {
throw new Error ( "Order not found" );
}
// 2. ์ฃผ๋ฌธ ํญ๋ชฉ ์กฐํ
const items = await trx . table ( "order_items" )
. where ( "order_id" , orderId )
. select ();
// 3. ์ฌ๊ณ ํ์ธ ๋ฐ ์ฐจ๊ฐ
for ( const item of items ) {
const [ product ] = await trx . table ( "products" )
. where ( "id" , item . product_id )
. forUpdate ()
. select ();
if ( product . stock < item . quantity ) {
throw new Error ( `Insufficient stock for ${ product . name } ` );
}
await trx . table ( "products" )
. where ( "id" , item . product_id )
. decrement ( "stock" , item . quantity );
}
// 4. ์ฃผ๋ฌธ ์ํ ์
๋ฐ์ดํธ
await trx . table ( "orders" )
. where ( "id" , orderId )
. update ({
status: "processing" ,
processed_at: new Date ()
});
// 5. ํ์คํ ๋ฆฌ ๊ธฐ๋ก
await trx . table ( "order_history" ). insert ({
order_id: orderId ,
status: "processing" ,
created_at: new Date ()
});
return { success: true };
});
}
}
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" );
์์ 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 ({ ... });
}
๋ค์ ๋จ๊ณ