128 lines
2.9 KiB
TypeScript
128 lines
2.9 KiB
TypeScript
|
|
import { Pool, PoolClient, QueryResult } from 'pg';
|
||
|
|
import config from '../config/environment';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* PostgreSQL Raw Query 서비스
|
||
|
|
* Prisma 대신 직접 pg 라이브러리를 사용
|
||
|
|
*/
|
||
|
|
export class PostgreSQLService {
|
||
|
|
private static pool: Pool;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 데이터베이스 연결 풀 초기화
|
||
|
|
*/
|
||
|
|
static initialize() {
|
||
|
|
if (!this.pool) {
|
||
|
|
this.pool = new Pool({
|
||
|
|
connectionString: config.databaseUrl,
|
||
|
|
max: 20, // 최대 연결 수
|
||
|
|
idleTimeoutMillis: 30000,
|
||
|
|
connectionTimeoutMillis: 2000,
|
||
|
|
});
|
||
|
|
|
||
|
|
// 연결 풀 이벤트 리스너
|
||
|
|
this.pool.on('connect', () => {
|
||
|
|
console.log('🔗 PostgreSQL 연결 성공');
|
||
|
|
});
|
||
|
|
|
||
|
|
this.pool.on('error', (err) => {
|
||
|
|
console.error('❌ PostgreSQL 연결 오류:', err);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 연결 풀 가져오기
|
||
|
|
*/
|
||
|
|
static getPool(): Pool {
|
||
|
|
if (!this.pool) {
|
||
|
|
this.initialize();
|
||
|
|
}
|
||
|
|
return this.pool;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 단일 쿼리 실행
|
||
|
|
*/
|
||
|
|
static async query(text: string, params?: any[]): Promise<QueryResult> {
|
||
|
|
const pool = this.getPool();
|
||
|
|
const start = Date.now();
|
||
|
|
|
||
|
|
try {
|
||
|
|
const result = await pool.query(text, params);
|
||
|
|
const duration = Date.now() - start;
|
||
|
|
|
||
|
|
if (config.debug) {
|
||
|
|
console.log('🔍 Query executed:', { text, duration: `${duration}ms`, rows: result.rowCount });
|
||
|
|
}
|
||
|
|
|
||
|
|
return result;
|
||
|
|
} catch (error) {
|
||
|
|
console.error('❌ Query error:', { text, params, error });
|
||
|
|
throw error;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 트랜잭션 실행
|
||
|
|
*/
|
||
|
|
static async transaction<T>(callback: (client: PoolClient) => Promise<T>): Promise<T> {
|
||
|
|
const pool = this.getPool();
|
||
|
|
const client = await pool.connect();
|
||
|
|
|
||
|
|
try {
|
||
|
|
await client.query('BEGIN');
|
||
|
|
const result = await callback(client);
|
||
|
|
await client.query('COMMIT');
|
||
|
|
return result;
|
||
|
|
} catch (error) {
|
||
|
|
await client.query('ROLLBACK');
|
||
|
|
throw error;
|
||
|
|
} finally {
|
||
|
|
client.release();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 연결 테스트
|
||
|
|
*/
|
||
|
|
static async testConnection(): Promise<boolean> {
|
||
|
|
try {
|
||
|
|
const result = await this.query('SELECT NOW() as current_time');
|
||
|
|
console.log('✅ PostgreSQL 연결 테스트 성공:', result.rows[0]);
|
||
|
|
return true;
|
||
|
|
} catch (error) {
|
||
|
|
console.error('❌ PostgreSQL 연결 테스트 실패:', error);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 연결 풀 종료
|
||
|
|
*/
|
||
|
|
static async close(): Promise<void> {
|
||
|
|
if (this.pool) {
|
||
|
|
await this.pool.end();
|
||
|
|
console.log('🔒 PostgreSQL 연결 풀 종료');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 애플리케이션 시작 시 초기화
|
||
|
|
PostgreSQLService.initialize();
|
||
|
|
|
||
|
|
// 프로세스 종료 시 연결 정리
|
||
|
|
process.on('SIGINT', async () => {
|
||
|
|
await PostgreSQLService.close();
|
||
|
|
process.exit(0);
|
||
|
|
});
|
||
|
|
|
||
|
|
process.on('SIGTERM', async () => {
|
||
|
|
await PostgreSQLService.close();
|
||
|
|
process.exit(0);
|
||
|
|
});
|
||
|
|
|
||
|
|
process.on('beforeExit', async () => {
|
||
|
|
await PostgreSQLService.close();
|
||
|
|
});
|