# Phase 1: Raw Query 기반 구조 사용 가이드 ## 📋 개요 Phase 1에서 구현한 Raw Query 기반 데이터베이스 아키텍처 사용 방법입니다. --- ## 🏗️ 구현된 모듈 ### 1. **DatabaseManager** (`src/database/db.ts`) PostgreSQL 연결 풀 기반 핵심 모듈 **주요 함수:** - `query(sql, params)` - 기본 쿼리 실행 - `queryOne(sql, params)` - 단일 행 조회 - `transaction(callback)` - 트랜잭션 실행 - `getPool()` - 연결 풀 가져오기 - `getPoolStatus()` - 연결 풀 상태 확인 ### 2. **QueryBuilder** (`src/utils/queryBuilder.ts`) 동적 쿼리 생성 유틸리티 **주요 메서드:** - `QueryBuilder.select(tableName, options)` - SELECT 쿼리 - `QueryBuilder.insert(tableName, data, options)` - INSERT 쿼리 - `QueryBuilder.update(tableName, data, where, options)` - UPDATE 쿼리 - `QueryBuilder.delete(tableName, where, options)` - DELETE 쿼리 - `QueryBuilder.count(tableName, where)` - COUNT 쿼리 - `QueryBuilder.exists(tableName, where)` - EXISTS 쿼리 ### 3. **DatabaseValidator** (`src/utils/databaseValidator.ts`) SQL Injection 방지 및 입력 검증 **주요 메서드:** - `validateTableName(tableName)` - 테이블명 검증 - `validateColumnName(columnName)` - 컬럼명 검증 - `validateWhereClause(where)` - WHERE 조건 검증 - `sanitizeInput(input)` - 입력 값 Sanitize ### 4. **타입 정의** (`src/types/database.ts`) TypeScript 타입 안전성 보장 --- ## 🚀 사용 예제 ### 1. 기본 쿼리 실행 ```typescript import { query, queryOne } from '../database/db'; // 여러 행 조회 const users = await query( 'SELECT * FROM users WHERE status = $1', ['active'] ); // 단일 행 조회 const user = await queryOne( 'SELECT * FROM users WHERE user_id = $1', ['user123'] ); if (!user) { throw new Error('사용자를 찾을 수 없습니다.'); } ``` ### 2. QueryBuilder 사용 #### SELECT ```typescript import { query } from '../database/db'; import { QueryBuilder } from '../utils/queryBuilder'; // 기본 SELECT const { query: sql, params } = QueryBuilder.select('users', { where: { status: 'active' }, orderBy: 'created_at DESC', limit: 10, }); const users = await query(sql, params); // 복잡한 SELECT (JOIN, WHERE, ORDER BY) const { query: sql2, params: params2 } = QueryBuilder.select('users', { columns: ['users.user_id', 'users.username', 'departments.dept_name'], joins: [ { type: 'LEFT', table: 'departments', on: 'users.dept_id = departments.dept_id', }, ], where: { 'users.status': 'active' }, orderBy: ['users.created_at DESC', 'users.username ASC'], limit: 20, offset: 0, }); const result = await query(sql2, params2); ``` #### INSERT ```typescript import { query } from '../database/db'; import { QueryBuilder } from '../utils/queryBuilder'; // 기본 INSERT const { query: sql, params } = QueryBuilder.insert( 'users', { user_id: 'new_user', username: 'John Doe', email: 'john@example.com', status: 'active', }, { returning: ['id', 'user_id'], } ); const [newUser] = await query(sql, params); console.log('생성된 사용자 ID:', newUser.id); // UPSERT (INSERT ... ON CONFLICT) const { query: sql2, params: params2 } = QueryBuilder.insert( 'users', { user_id: 'user123', username: 'Jane', email: 'jane@example.com', }, { onConflict: { columns: ['user_id'], action: 'DO UPDATE', updateSet: ['username', 'email'], }, returning: ['*'], } ); const [upsertedUser] = await query(sql2, params2); ``` #### UPDATE ```typescript import { query } from '../database/db'; import { QueryBuilder } from '../utils/queryBuilder'; const { query: sql, params } = QueryBuilder.update( 'users', { username: 'Updated Name', email: 'updated@example.com', updated_at: new Date(), }, { user_id: 'user123', }, { returning: ['*'], } ); const [updatedUser] = await query(sql, params); ``` #### DELETE ```typescript import { query } from '../database/db'; import { QueryBuilder } from '../utils/queryBuilder'; const { query: sql, params } = QueryBuilder.delete( 'users', { user_id: 'user_to_delete', }, { returning: ['user_id', 'username'], } ); const [deletedUser] = await query(sql, params); console.log('삭제된 사용자:', deletedUser.username); ``` ### 3. 트랜잭션 사용 ```typescript import { transaction } from '../database/db'; // 복잡한 트랜잭션 처리 const result = await transaction(async (client) => { // 1. 사용자 생성 const userResult = await client.query( 'INSERT INTO users (user_id, username, email) VALUES ($1, $2, $3) RETURNING id', ['new_user', 'John', 'john@example.com'] ); const userId = userResult.rows[0].id; // 2. 역할 할당 await client.query( 'INSERT INTO user_roles (user_id, role_id) VALUES ($1, $2)', [userId, 'admin'] ); // 3. 로그 생성 await client.query( 'INSERT INTO audit_logs (action, user_id, details) VALUES ($1, $2, $3)', ['USER_CREATED', userId, JSON.stringify({ username: 'John' })] ); return { success: true, userId }; }); console.log('트랜잭션 완료:', result); ``` ### 4. JSON 필드 쿼리 (JSONB) ```typescript import { query } from '../database/db'; import { QueryBuilder } from '../utils/queryBuilder'; // JSON 필드 쿼리 (config->>'type' = 'form') const { query: sql, params } = QueryBuilder.select('screen_management', { columns: ['*'], where: { company_code: 'COMPANY_001', "config->>'type'": 'form', }, }); const screens = await query(sql, params); ``` ### 5. 동적 테이블 쿼리 ```typescript import { query } from '../database/db'; import { DatabaseValidator } from '../utils/databaseValidator'; async function queryDynamicTable(tableName: string, filters: Record) { // 테이블명 검증 (SQL Injection 방지) if (!DatabaseValidator.validateTableName(tableName)) { throw new Error('유효하지 않은 테이블명입니다.'); } // WHERE 조건 검증 if (!DatabaseValidator.validateWhereClause(filters)) { throw new Error('유효하지 않은 WHERE 조건입니다.'); } const { query: sql, params } = QueryBuilder.select(tableName, { where: filters, }); return await query(sql, params); } // 사용 예 const data = await queryDynamicTable('company_data_001', { status: 'active', region: 'Seoul', }); ``` --- ## 🔐 보안 고려사항 ### 1. **항상 Parameterized Query 사용** ```typescript // ❌ 위험: SQL Injection 취약 const userId = req.params.userId; const sql = `SELECT * FROM users WHERE user_id = '${userId}'`; const users = await query(sql); // ✅ 안전: Parameterized Query const userId = req.params.userId; const users = await query('SELECT * FROM users WHERE user_id = $1', [userId]); ``` ### 2. **식별자 검증** ```typescript import { DatabaseValidator } from '../utils/databaseValidator'; // 테이블명/컬럼명 검증 if (!DatabaseValidator.validateTableName(tableName)) { throw new Error('유효하지 않은 테이블명입니다.'); } if (!DatabaseValidator.validateColumnName(columnName)) { throw new Error('유효하지 않은 컬럼명입니다.'); } ``` ### 3. **입력 값 Sanitize** ```typescript import { DatabaseValidator } from '../utils/databaseValidator'; const sanitizedData = DatabaseValidator.sanitizeInput(userInput); ``` --- ## 📊 성능 최적화 팁 ### 1. **연결 풀 모니터링** ```typescript import { getPoolStatus } from '../database/db'; const status = getPoolStatus(); console.log('연결 풀 상태:', { total: status.totalCount, idle: status.idleCount, waiting: status.waitingCount, }); ``` ### 2. **배치 INSERT** ```typescript import { transaction } from '../database/db'; // 대량 데이터 삽입 시 트랜잭션 사용 await transaction(async (client) => { for (const item of largeDataset) { await client.query('INSERT INTO items (name, value) VALUES ($1, $2)', [ item.name, item.value, ]); } }); ``` ### 3. **인덱스 활용 쿼리** ```typescript // WHERE 절에 인덱스 컬럼 사용 const { query: sql, params } = QueryBuilder.select('users', { where: { user_id: 'user123', // 인덱스 컬럼 }, }); ``` --- ## 🧪 테스트 실행 ```bash # 테스트 실행 npm test -- database.test.ts # 특정 테스트만 실행 npm test -- database.test.ts -t "QueryBuilder" ``` --- ## 🚨 에러 핸들링 ```typescript import { query } from '../database/db'; try { const users = await query('SELECT * FROM users WHERE status = $1', ['active']); return users; } catch (error: any) { console.error('쿼리 실행 실패:', error.message); // PostgreSQL 에러 코드 확인 if (error.code === '23505') { throw new Error('중복된 값이 존재합니다.'); } if (error.code === '23503') { throw new Error('외래 키 제약 조건 위반입니다.'); } throw error; } ``` --- ## 📝 다음 단계 (Phase 2) Phase 1 기반 구조가 완성되었으므로, Phase 2에서는: 1. **screenManagementService.ts** 전환 (46개 호출) 2. **tableManagementService.ts** 전환 (35개 호출) 3. **dataflowService.ts** 전환 (31개 호출) 등 핵심 서비스를 Raw Query로 전환합니다. --- **작성일**: 2025-09-30 **버전**: 1.0.0 **담당**: Backend Development Team