12 KiB
12 KiB
🗂️ Phase 2.2: TableManagementService Raw Query 전환 계획
📋 개요
TableManagementService는 33개의 Prisma 호출이 있습니다. 대부분(약 26개)은 $queryRaw를 사용하고 있어 SQL은 이미 작성되어 있지만, Prisma 클라이언트를 완전히 제거하려면 33개 모두를 db.ts의 query 함수로 교체해야 합니다.
📊 기본 정보
| 항목 | 내용 |
|---|---|
| 파일 위치 | backend-node/src/services/tableManagementService.ts |
| 파일 크기 | 3,178 라인 |
| Prisma 호출 | 33개 ($queryRaw: 26개, ORM: 7개) |
| 현재 진행률 | 0/33 (0%) ⏳ 전환 필요 |
| 전환 필요 | 33개 모두 전환 필요 (SQL은 이미 작성되어 있음) |
| 복잡도 | 중간 (SQL 작성은 완료, query() 함수로 교체만 필요) |
| 우선순위 | 🟡 중간 (Phase 2.2) |
🎯 전환 목표
- ✅ 33개 모든 Prisma 호출을
db.ts의query()함수로 교체- 26개
$queryRaw→query()또는queryOne() - 7개 ORM 메서드 →
query()(SQL 새로 작성) - 1개
$transaction→transaction()
- 26개
- ✅ 트랜잭션 처리 정상 동작 확인
- ✅ 모든 단위 테스트 통과
- ✅ Prisma import 완전 제거
🔍 Prisma 사용 현황 분석
1. $queryRaw / $queryRawUnsafe 사용 (26개)
현재 상태: SQL은 이미 작성되어 있음 ✅
전환 작업: prisma.$queryRaw → query() 함수로 교체만 하면 됨
// 기존
await prisma.$queryRaw`SELECT ...`;
await prisma.$queryRawUnsafe(sqlString, ...params);
// 전환 후
import { query } from "../database/db";
await query(`SELECT ...`);
await query(sqlString, params);
2. ORM 메서드 사용 (7개)
현재 상태: Prisma ORM 메서드 사용
전환 작업: SQL 작성 필요
1. table_labels 관리 (2개)
// Line 254: 테이블 라벨 UPSERT
await prisma.table_labels.upsert({
where: { table_name: tableName },
update: {},
create: { table_name, table_label, description }
});
// Line 437: 테이블 라벨 조회
await prisma.table_labels.findUnique({
where: { table_name: tableName },
select: { table_name, table_label, description, ... }
});
2. column_labels 관리 (5개)
// Line 323: 컬럼 라벨 UPSERT
await prisma.column_labels.upsert({
where: {
table_name_column_name: {
table_name: tableName,
column_name: columnName
}
},
update: { column_label, input_type, ... },
create: { table_name, column_name, ... }
});
// Line 481: 컬럼 라벨 조회
await prisma.column_labels.findUnique({
where: {
table_name_column_name: {
table_name: tableName,
column_name: columnName
}
},
select: { id, table_name, column_name, ... }
});
// Line 567: 컬럼 존재 확인
await prisma.column_labels.findFirst({
where: { table_name, column_name }
});
// Line 586: 컬럼 라벨 업데이트
await prisma.column_labels.update({
where: { id: existingColumn.id },
data: { web_type, detail_settings, ... }
});
// Line 610: 컬럼 라벨 생성
await prisma.column_labels.create({
data: { table_name, column_name, web_type, ... }
});
// Line 1003: 파일 타입 컬럼 조회
await prisma.column_labels.findMany({
where: { table_name, web_type: 'file' },
select: { column_name }
});
// Line 1382: 컬럼 웹타입 정보 조회
await prisma.column_labels.findFirst({
where: { table_name, column_name },
select: { web_type, code_category, ... }
});
// Line 2690: 컬럼 라벨 UPSERT (복제)
await prisma.column_labels.upsert({
where: {
table_name_column_name: { table_name, column_name }
},
update: { column_label, web_type, ... },
create: { table_name, column_name, ... }
});
3. attach_file_info 관리 (2개)
// Line 914: 파일 정보 조회
await prisma.attach_file_info.findMany({
where: { target_objid, doc_type, status: 'ACTIVE' },
select: { objid, real_file_name, file_size, ... },
orderBy: { regdate: 'desc' }
});
// Line 959: 파일 경로로 파일 정보 조회
await prisma.attach_file_info.findFirst({
where: { file_path, status: 'ACTIVE' },
select: { objid, real_file_name, ... }
});
4. 트랜잭션 (1개)
// Line 391: 전체 컬럼 설정 일괄 업데이트
await prisma.$transaction(async (tx) => {
await this.insertTableIfNotExists(tableName);
for (const columnSetting of columnSettings) {
await this.updateColumnSettings(tableName, columnName, columnSetting);
}
});
📝 전환 예시
예시 1: table_labels UPSERT 전환
기존 Prisma 코드:
await prisma.table_labels.upsert({
where: { table_name: tableName },
update: {},
create: {
table_name: tableName,
table_label: tableName,
description: "",
},
});
새로운 Raw Query 코드:
import { query } from "../database/db";
await query(
`INSERT INTO table_labels (table_name, table_label, description, created_date, updated_date)
VALUES ($1, $2, $3, NOW(), NOW())
ON CONFLICT (table_name) DO NOTHING`,
[tableName, tableName, ""]
);
예시 2: column_labels UPSERT 전환
기존 Prisma 코드:
await prisma.column_labels.upsert({
where: {
table_name_column_name: {
table_name: tableName,
column_name: columnName,
},
},
update: {
column_label: settings.columnLabel,
input_type: settings.inputType,
detail_settings: settings.detailSettings,
updated_date: new Date(),
},
create: {
table_name: tableName,
column_name: columnName,
column_label: settings.columnLabel,
input_type: settings.inputType,
detail_settings: settings.detailSettings,
},
});
새로운 Raw Query 코드:
await query(
`INSERT INTO column_labels (
table_name, column_name, column_label, input_type, detail_settings,
code_category, code_value, reference_table, reference_column,
display_column, display_order, is_visible, created_date, updated_date
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, NOW(), NOW())
ON CONFLICT (table_name, column_name)
DO UPDATE SET
column_label = EXCLUDED.column_label,
input_type = EXCLUDED.input_type,
detail_settings = EXCLUDED.detail_settings,
code_category = EXCLUDED.code_category,
code_value = EXCLUDED.code_value,
reference_table = EXCLUDED.reference_table,
reference_column = EXCLUDED.reference_column,
display_column = EXCLUDED.display_column,
display_order = EXCLUDED.display_order,
is_visible = EXCLUDED.is_visible,
updated_date = NOW()`,
[
tableName,
columnName,
settings.columnLabel,
settings.inputType,
settings.detailSettings,
settings.codeCategory,
settings.codeValue,
settings.referenceTable,
settings.referenceColumn,
settings.displayColumn,
settings.displayOrder || 0,
settings.isVisible !== undefined ? settings.isVisible : true,
]
);
예시 3: 트랜잭션 전환
기존 Prisma 코드:
await prisma.$transaction(async (tx) => {
await this.insertTableIfNotExists(tableName);
for (const columnSetting of columnSettings) {
await this.updateColumnSettings(tableName, columnName, columnSetting);
}
});
새로운 Raw Query 코드:
import { transaction } from "../database/db";
await transaction(async (client) => {
// 테이블 라벨 자동 추가
await client.query(
`INSERT INTO table_labels (table_name, table_label, description, created_date, updated_date)
VALUES ($1, $2, $3, NOW(), NOW())
ON CONFLICT (table_name) DO NOTHING`,
[tableName, tableName, ""]
);
// 각 컬럼 설정 업데이트
for (const columnSetting of columnSettings) {
const columnName = columnSetting.columnName;
if (columnName) {
await client.query(
`INSERT INTO column_labels (...)
VALUES (...)
ON CONFLICT (table_name, column_name) DO UPDATE SET ...`,
[...]
);
}
}
});
🧪 테스트 계획
단위 테스트 (10개)
describe("TableManagementService Raw Query 전환 테스트", () => {
describe("insertTableIfNotExists", () => {
test("테이블 라벨 UPSERT 성공", async () => { ... });
test("중복 테이블 처리", async () => { ... });
});
describe("updateColumnSettings", () => {
test("컬럼 설정 UPSERT 성공", async () => { ... });
test("기존 컬럼 업데이트", async () => { ... });
});
describe("getTableLabels", () => {
test("테이블 라벨 조회 성공", async () => { ... });
});
describe("getColumnLabels", () => {
test("컬럼 라벨 조회 성공", async () => { ... });
});
describe("updateAllColumnSettings", () => {
test("일괄 업데이트 성공 (트랜잭션)", async () => { ... });
test("부분 실패 시 롤백", async () => { ... });
});
describe("getFileInfoByColumnAndTarget", () => {
test("파일 정보 조회 성공", async () => { ... });
});
});
통합 테스트 (5개 시나리오)
describe("테이블 관리 통합 테스트", () => {
test("테이블 라벨 생성 → 조회 → 수정", async () => { ... });
test("컬럼 라벨 생성 → 조회 → 수정", async () => { ... });
test("컬럼 일괄 설정 업데이트", async () => { ... });
test("파일 정보 조회 및 보강", async () => { ... });
test("트랜잭션 롤백 테스트", async () => { ... });
});
📋 체크리스트
1단계: table_labels 전환 (2개 함수) ⏳ 진행 예정
insertTableIfNotExists()- UPSERTgetTableLabels()- 조회
2단계: column_labels 전환 (5개 함수) ⏳ 진행 예정
updateColumnSettings()- UPSERTgetColumnLabels()- 조회updateColumnWebType()- findFirst + update/creategetColumnWebTypeInfo()- findFirstupdateColumnLabel()- UPSERT (복제)
3단계: attach_file_info 전환 (2개 함수) ⏳ 진행 예정
getFileInfoByColumnAndTarget()- findManygetFileInfoByPath()- findFirst
4단계: 트랜잭션 전환 (1개 함수) ⏳ 진행 예정
updateAllColumnSettings()- 트랜잭션
5단계: 테스트 & 검증 ⏳ 진행 예정
- 단위 테스트 작성 (10개)
- 통합 테스트 작성 (5개 시나리오)
- Prisma import 완전 제거 확인
- 성능 테스트
🎯 완료 기준
- 33개 모든 Prisma 호출을 Raw Query로 전환 완료
- 26개
$queryRaw→query()함수로 교체 - 7개 ORM 메서드 →
query()함수로 전환 (SQL 작성)
- 26개
- 모든 TypeScript 컴파일 오류 해결
- 트랜잭션 정상 동작 확인
- 에러 처리 및 롤백 정상 동작
- 모든 단위 테스트 통과 (10개)
- 모든 통합 테스트 작성 완료 (5개 시나리오)
import prisma완전 제거 및import { query, transaction } from "../database/db"사용- 성능 저하 없음 (기존 대비 ±10% 이내)
💡 특이사항
SQL은 이미 대부분 작성되어 있음
이 서비스는 이미 79%가 $queryRaw를 사용하고 있어, SQL 작성은 완료되었습니다:
- ✅
information_schema조회: SQL 작성 완료 ($queryRaw사용 중) - ✅ 동적 테이블 쿼리: SQL 작성 완료 (
$queryRawUnsafe사용 중) - ✅ DDL 실행: SQL 작성 완료 (
$executeRaw사용 중) - ⏳ 전환 작업:
prisma.$queryRaw→query()함수로 단순 교체만 필요 - ⏳ CRUD 작업: 7개만 SQL 새로 작성 필요
UPSERT 패턴 중요
대부분의 전환이 UPSERT 패턴이므로 PostgreSQL의 ON CONFLICT 구문을 활용합니다.
작성일: 2025-09-30
예상 소요 시간: 1-1.5일 (SQL은 79% 작성 완료, 함수 교체 작업 필요)
담당자: 백엔드 개발팀
우선순위: 🟡 중간 (Phase 2.2)
상태: ⏳ 진행 예정
특이사항: SQL은 대부분 작성되어 있어 prisma.$queryRaw → query() 단순 교체 작업이 주요 작업