feat: Phase 3.8 DbTypeCategoryService Raw Query 전환 완료

10개 Prisma 호출을 모두 Raw Query로 전환
- 카테고리 목록 조회 (getAllCategories)
- 카테고리 단건 조회 (getCategoryByTypeCode)
- 카테고리 생성 (createCategory - 중복 검사)
- 카테고리 수정 (updateCategory - 동적 UPDATE)
- 카테고리 삭제 (deleteCategory - 연결 확인 후 비활성화)
- 연결 통계 조회 (getConnectionStatsByType - LEFT JOIN + GROUP BY)
- 기본 카테고리 초기화 (initializeDefaultCategories - UPSERT)

주요 기술적 해결:
- ApiResponse 래퍼 패턴 유지
- 동적 UPDATE 쿼리 (5개 필드 조건부 업데이트)
- ON CONFLICT를 사용한 UPSERT (기본 카테고리 초기화)
- 연결 확인 (external_db_connections COUNT)
- LEFT JOIN + GROUP BY 통계 쿼리 최적화 (타입별 연결 수)
- 중복 검사 (카테고리 생성 시)
- try-catch 에러 처리 및 ApiResponse 반환

TypeScript 컴파일 성공
Prisma import 완전 제거

Phase 3 진행률: 107/162 (66.0%)
전체 진행률: 358/444 (80.6%)
This commit is contained in:
kjs 2025-10-01 11:32:45 +09:00
parent c973cb674d
commit a8c4f9ec45
2 changed files with 119 additions and 78 deletions

View File

@ -129,8 +129,8 @@ backend-node/ (루트)
- `dataflowDiagramService.ts` (0개) - ✅ **전환 완료** (Phase 3.5)
- `collectionService.ts` (0개) - ✅ **전환 완료** (Phase 3.6)
- `layoutService.ts` (0개) - ✅ **전환 완료** (Phase 3.7)
- `dbTypeCategoryService.ts` (10개) - DB 타입 분류 ⭐ 신규 발견
- `templateStandardService.ts` (9개) - 템플릿 표준
- `dbTypeCategoryService.ts` (0개) - ✅ **전환 완료** (Phase 3.8)
- `templateStandardService.ts` (6개) - 템플릿 표준
- `eventTriggerService.ts` (6개) - JSON 검색 쿼리
#### 🟡 **중간 (단순 CRUD) - 3순위**
@ -1194,6 +1194,16 @@ describe("Performance Benchmarks", () => {
- [x] Promise.all 병렬 쿼리 (목록 + 개수)
- [x] TypeScript 컴파일 성공
- [x] Prisma import 완전 제거
- [x] **DbTypeCategoryService 전환 (10개)****완료** (Phase 3.8)
- [x] 10개 Prisma 호출 전환 완료 (DB 타입 카테고리 CRUD, 통계)
- [x] ApiResponse 래퍼 패턴 유지
- [x] 동적 UPDATE 쿼리 (5개 필드 조건부 업데이트)
- [x] ON CONFLICT를 사용한 UPSERT (기본 카테고리 초기화)
- [x] 연결 확인 (external_db_connections COUNT)
- [x] LEFT JOIN + GROUP BY 통계 쿼리 (타입별 연결 수)
- [x] 중복 검사 (카테고리 생성 시)
- [x] TypeScript 컴파일 성공
- [x] Prisma import 완전 제거
- [ ] 배치 관련 서비스 전환 (26개) ⭐ 대규모 신규 발견
- [ ] BatchExternalDbService (8개)
- [ ] BatchExecutionLogService (7개), BatchManagementService (5개)
@ -1202,8 +1212,7 @@ describe("Performance Benchmarks", () => {
- [ ] TemplateStandardService (6개) - [계획서](PHASE3.9_TEMPLATE_STANDARD_SERVICE_MIGRATION.md)
- [ ] 데이터플로우 관련 서비스 (6개) ⭐ 신규 발견
- [ ] DataflowControlService (6개)
- [ ] 기타 중요 서비스 (18개) ⭐ 신규 발견
- [ ] DbTypeCategoryService (10개) - [계획서](PHASE3.8_DB_TYPE_CATEGORY_SERVICE_MIGRATION.md)
- [ ] 기타 중요 서비스 (8개) ⭐ 신규 발견
- [ ] DDLAuditLogger (8개)
- [ ] 기능별 테스트 완료

View File

@ -1,6 +1,4 @@
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
import { query, queryOne } from "../database/db";
export interface DbTypeCategory {
type_code: string;
@ -42,13 +40,12 @@ export class DbTypeCategoryService {
*/
static async getAllCategories(): Promise<ApiResponse<DbTypeCategory[]>> {
try {
const categories = await prisma.db_type_categories.findMany({
where: { is_active: true },
orderBy: [
{ sort_order: 'asc' },
{ display_name: 'asc' }
]
});
const categories = await query<DbTypeCategory>(
`SELECT * FROM db_type_categories
WHERE is_active = $1
ORDER BY sort_order ASC, display_name ASC`,
[true]
);
return {
success: true,
@ -70,9 +67,10 @@ export class DbTypeCategoryService {
*/
static async getCategoryByTypeCode(typeCode: string): Promise<ApiResponse<DbTypeCategory>> {
try {
const category = await prisma.db_type_categories.findUnique({
where: { type_code: typeCode }
});
const category = await queryOne<DbTypeCategory>(
`SELECT * FROM db_type_categories WHERE type_code = $1`,
[typeCode]
);
if (!category) {
return {
@ -102,9 +100,10 @@ export class DbTypeCategoryService {
static async createCategory(data: CreateDbTypeCategoryRequest): Promise<ApiResponse<DbTypeCategory>> {
try {
// 중복 체크
const existing = await prisma.db_type_categories.findUnique({
where: { type_code: data.type_code }
});
const existing = await queryOne<DbTypeCategory>(
`SELECT * FROM db_type_categories WHERE type_code = $1`,
[data.type_code]
);
if (existing) {
return {
@ -113,15 +112,20 @@ export class DbTypeCategoryService {
};
}
const category = await prisma.db_type_categories.create({
data: {
type_code: data.type_code,
display_name: data.display_name,
icon: data.icon,
color: data.color,
sort_order: data.sort_order || 0
}
});
const category = await queryOne<DbTypeCategory>(
`INSERT INTO db_type_categories
(type_code, display_name, icon, color, sort_order, is_active, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, NOW(), NOW())
RETURNING *`,
[
data.type_code,
data.display_name,
data.icon || null,
data.color || null,
data.sort_order || 0,
true,
]
);
return {
success: true,
@ -143,17 +147,39 @@ export class DbTypeCategoryService {
*/
static async updateCategory(typeCode: string, data: UpdateDbTypeCategoryRequest): Promise<ApiResponse<DbTypeCategory>> {
try {
const category = await prisma.db_type_categories.update({
where: { type_code: typeCode },
data: {
display_name: data.display_name,
icon: data.icon,
color: data.color,
sort_order: data.sort_order,
is_active: data.is_active,
updated_at: new Date()
}
});
// 동적 UPDATE 쿼리 생성
const updateFields: string[] = ["updated_at = NOW()"];
const values: any[] = [];
let paramIndex = 1;
if (data.display_name !== undefined) {
updateFields.push(`display_name = $${paramIndex++}`);
values.push(data.display_name);
}
if (data.icon !== undefined) {
updateFields.push(`icon = $${paramIndex++}`);
values.push(data.icon);
}
if (data.color !== undefined) {
updateFields.push(`color = $${paramIndex++}`);
values.push(data.color);
}
if (data.sort_order !== undefined) {
updateFields.push(`sort_order = $${paramIndex++}`);
values.push(data.sort_order);
}
if (data.is_active !== undefined) {
updateFields.push(`is_active = $${paramIndex++}`);
values.push(data.is_active);
}
const category = await queryOne<DbTypeCategory>(
`UPDATE db_type_categories
SET ${updateFields.join(", ")}
WHERE type_code = $${paramIndex}
RETURNING *`,
[...values, typeCode]
);
return {
success: true,
@ -176,12 +202,12 @@ export class DbTypeCategoryService {
static async deleteCategory(typeCode: string): Promise<ApiResponse<void>> {
try {
// 해당 타입을 사용하는 연결이 있는지 확인
const connectionsCount = await prisma.external_db_connections.count({
where: {
db_type: typeCode,
is_active: "Y"
}
});
const countResult = await queryOne<{ count: string }>(
`SELECT COUNT(*) as count FROM external_db_connections
WHERE db_type = $1 AND is_active = $2`,
[typeCode, "Y"]
);
const connectionsCount = parseInt(countResult?.count || "0");
if (connectionsCount > 0) {
return {
@ -190,13 +216,12 @@ export class DbTypeCategoryService {
};
}
await prisma.db_type_categories.update({
where: { type_code: typeCode },
data: {
is_active: false,
updated_at: new Date()
}
});
await query(
`UPDATE db_type_categories
SET is_active = $1, updated_at = NOW()
WHERE type_code = $2`,
[false, typeCode]
);
return {
success: true,
@ -217,30 +242,28 @@ export class DbTypeCategoryService {
*/
static async getConnectionStatsByType(): Promise<ApiResponse<any[]>> {
try {
const stats = await prisma.external_db_connections.groupBy({
by: ['db_type'],
where: { is_active: "Y" },
_count: {
id: true
}
});
// LEFT JOIN으로 한 번에 조회
const result = await query<any>(
`SELECT
c.*,
COUNT(e.id) as connection_count
FROM db_type_categories c
LEFT JOIN external_db_connections e ON c.type_code = e.db_type AND e.is_active = $1
WHERE c.is_active = $2
GROUP BY c.type_code, c.display_name, c.icon, c.color, c.sort_order, c.is_active, c.created_at, c.updated_at
ORDER BY c.sort_order ASC`,
["Y", true]
);
// 카테고리 정보와 함께 반환
const categories = await prisma.db_type_categories.findMany({
where: { is_active: true }
});
const result = categories.map(category => {
const stat = stats.find(s => s.db_type === category.type_code);
return {
...category,
connection_count: stat?._count.id || 0
};
});
// connection_count를 숫자로 변환
const formattedResult = result.map(row => ({
...row,
connection_count: parseInt(row.connection_count)
}));
return {
success: true,
data: result,
data: formattedResult,
message: "DB 타입별 연결 통계를 조회했습니다."
};
} catch (error) {
@ -297,11 +320,20 @@ export class DbTypeCategoryService {
];
for (const category of defaultCategories) {
await prisma.db_type_categories.upsert({
where: { type_code: category.type_code },
update: {},
create: category
});
await query(
`INSERT INTO db_type_categories
(type_code, display_name, icon, color, sort_order, is_active, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, NOW(), NOW())
ON CONFLICT (type_code) DO NOTHING`,
[
category.type_code,
category.display_name,
category.icon,
category.color,
category.sort_order,
true,
]
);
}
return {