diff --git a/PRISMA_TO_RAW_QUERY_MIGRATION_PLAN.md b/PRISMA_TO_RAW_QUERY_MIGRATION_PLAN.md index 4d616005..3a689bfb 100644 --- a/PRISMA_TO_RAW_QUERY_MIGRATION_PLAN.md +++ b/PRISMA_TO_RAW_QUERY_MIGRATION_PLAN.md @@ -126,7 +126,7 @@ backend-node/ (루트) - `batchService.ts` (0개) - ✅ **전환 완료** (Phase 3.2) - `componentStandardService.ts` (0개) - ✅ **전환 완료** (Phase 3.3) - `commonCodeService.ts` (0개) - ✅ **전환 완료** (Phase 3.4) -- `dataflowDiagramService.ts` (12개) - 다이어그램 관리 ⭐ 신규 발견 +- `dataflowDiagramService.ts` (0개) - ✅ **전환 완료** (Phase 3.5) - `collectionService.ts` (11개) - 컬렉션 관리 - `layoutService.ts` (10개) - 레이아웃 관리 - `dbTypeCategoryService.ts` (10개) - DB 타입 분류 ⭐ 신규 발견 @@ -1165,14 +1165,23 @@ describe("Performance Benchmarks", () => { - [x] 동적 SQL 쿼리 생성 (중복 검사) - [x] TypeScript 컴파일 성공 - [x] Prisma import 완전 제거 +- [x] **DataflowDiagramService 전환 (12개)** ✅ **완료** (Phase 3.5) + - [x] 12개 Prisma 호출 전환 완료 (관계도 CRUD, 복제) + - [x] 동적 WHERE 조건 생성 (company_code 필터링) + - [x] 동적 UPDATE 쿼리 (JSON 필드 포함) + - [x] JSON 필드 처리 (relationships, node_positions, control, category, plan) + - [x] LIKE 검색 (복제 시 이름 패턴 검색) + - [x] 복잡한 복제 로직 (이름 번호 증가) + - [x] TypeScript 컴파일 성공 + - [x] Prisma import 완전 제거 - [ ] 배치 관련 서비스 전환 (26개) ⭐ 대규모 신규 발견 - [ ] BatchExternalDbService (8개) - [ ] BatchExecutionLogService (7개), BatchManagementService (5개) - [ ] BatchSchedulerService (4개) - [ ] 표준 관리 서비스 전환 (10개) - [ ] LayoutService (10개) -- [ ] 데이터플로우 관련 서비스 (18개) ⭐ 신규 발견 - - [ ] DataflowDiagramService (12개), DataflowControlService (6개) +- [ ] 데이터플로우 관련 서비스 (6개) ⭐ 신규 발견 + - [ ] DataflowControlService (6개) - [ ] 기타 중요 서비스 (38개) ⭐ 신규 발견 - [ ] CollectionService (11개), DbTypeCategoryService (10개) - [ ] TemplateStandardService (9개), DDLAuditLogger (8개) diff --git a/backend-node/src/services/dataflowDiagramService.ts b/backend-node/src/services/dataflowDiagramService.ts index 8531ec3d..6578c7ea 100644 --- a/backend-node/src/services/dataflowDiagramService.ts +++ b/backend-node/src/services/dataflowDiagramService.ts @@ -1,5 +1,4 @@ -import { Prisma } from "@prisma/client"; -import prisma from "../config/database"; +import { query, queryOne, transaction } from "../database/db"; import { logger } from "../utils/logger"; // 타입 정의 @@ -43,41 +42,41 @@ export const getDataflowDiagrams = async ( try { const offset = (page - 1) * size; - // 검색 조건 구성 - const whereClause: { - company_code?: string; - diagram_name?: { - contains: string; - mode: "insensitive"; - }; - } = {}; + const whereConditions: string[] = []; + const values: any[] = []; + let paramIndex = 1; // company_code가 '*'가 아닌 경우에만 필터링 if (companyCode !== "*") { - whereClause.company_code = companyCode; + whereConditions.push(`company_code = $${paramIndex++}`); + values.push(companyCode); } if (searchTerm) { - whereClause.diagram_name = { - contains: searchTerm, - mode: "insensitive", - }; + whereConditions.push(`diagram_name ILIKE $${paramIndex++}`); + values.push(`%${searchTerm}%`); } + const whereClause = + whereConditions.length > 0 + ? `WHERE ${whereConditions.join(" AND ")}` + : ""; + // 총 개수 조회 - const total = await prisma.dataflow_diagrams.count({ - where: whereClause, - }); + const countResult = await queryOne<{ count: string }>( + `SELECT COUNT(*) as count FROM dataflow_diagrams ${whereClause}`, + values + ); + const total = parseInt(countResult?.count || "0"); // 데이터 조회 - const diagrams = await prisma.dataflow_diagrams.findMany({ - where: whereClause, - orderBy: { - updated_at: "desc", - }, - skip: offset, - take: size, - }); + const diagrams = await query( + `SELECT * FROM dataflow_diagrams + ${whereClause} + ORDER BY updated_at DESC + LIMIT $${paramIndex++} OFFSET $${paramIndex++}`, + [...values, size, offset] + ); const totalPages = Math.ceil(total / size); @@ -104,21 +103,21 @@ export const getDataflowDiagramById = async ( companyCode: string ) => { try { - const whereClause: { - diagram_id: number; - company_code?: string; - } = { - diagram_id: diagramId, - }; + const whereConditions: string[] = ["diagram_id = $1"]; + const values: any[] = [diagramId]; // company_code가 '*'가 아닌 경우에만 필터링 if (companyCode !== "*") { - whereClause.company_code = companyCode; + whereConditions.push("company_code = $2"); + values.push(companyCode); } - const diagram = await prisma.dataflow_diagrams.findFirst({ - where: whereClause, - }); + const whereClause = `WHERE ${whereConditions.join(" AND ")}`; + + const diagram = await queryOne( + `SELECT * FROM dataflow_diagrams ${whereClause} LIMIT 1`, + values + ); return diagram; } catch (error) { @@ -134,23 +133,24 @@ export const createDataflowDiagram = async ( data: CreateDataflowDiagramData ) => { try { - const newDiagram = await prisma.dataflow_diagrams.create({ - data: { - diagram_name: data.diagram_name, - relationships: data.relationships as Prisma.InputJsonValue, - node_positions: data.node_positions as - | Prisma.InputJsonValue - | undefined, - category: data.category - ? (data.category as Prisma.InputJsonValue) - : undefined, - control: data.control as Prisma.InputJsonValue | undefined, - plan: data.plan as Prisma.InputJsonValue | undefined, - company_code: data.company_code, - created_by: data.created_by, - updated_by: data.updated_by, - }, - }); + const newDiagram = await queryOne( + `INSERT INTO dataflow_diagrams + (diagram_name, relationships, node_positions, category, control, plan, + company_code, created_by, updated_by, created_at, updated_at) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW(), NOW()) + RETURNING *`, + [ + data.diagram_name, + JSON.stringify(data.relationships), + data.node_positions ? JSON.stringify(data.node_positions) : null, + data.category ? JSON.stringify(data.category) : null, + data.control ? JSON.stringify(data.control) : null, + data.plan ? JSON.stringify(data.plan) : null, + data.company_code, + data.created_by, + data.updated_by, + ] + ); return newDiagram; } catch (error) { @@ -173,21 +173,18 @@ export const updateDataflowDiagram = async ( ); // 먼저 해당 관계도가 존재하는지 확인 - const whereClause: { - diagram_id: number; - company_code?: string; - } = { - diagram_id: diagramId, - }; + const whereConditions: string[] = ["diagram_id = $1"]; + const checkValues: any[] = [diagramId]; - // company_code가 '*'가 아닌 경우에만 필터링 if (companyCode !== "*") { - whereClause.company_code = companyCode; + whereConditions.push("company_code = $2"); + checkValues.push(companyCode); } - const existingDiagram = await prisma.dataflow_diagrams.findFirst({ - where: whereClause, - }); + const existingDiagram = await queryOne( + `SELECT * FROM dataflow_diagrams WHERE ${whereConditions.join(" AND ")} LIMIT 1`, + checkValues + ); logger.info( `기존 관계도 조회 결과:`, @@ -201,36 +198,45 @@ export const updateDataflowDiagram = async ( return null; } - // 업데이트 실행 - const updatedDiagram = await prisma.dataflow_diagrams.update({ - where: { - diagram_id: diagramId, - }, - data: { - ...(data.diagram_name && { diagram_name: data.diagram_name }), - ...(data.relationships && { - relationships: data.relationships as Prisma.InputJsonValue, - }), - ...(data.node_positions !== undefined && { - node_positions: data.node_positions - ? (data.node_positions as Prisma.InputJsonValue) - : Prisma.JsonNull, - }), - ...(data.category !== undefined && { - category: data.category - ? (data.category as Prisma.InputJsonValue) - : undefined, - }), - ...(data.control !== undefined && { - control: data.control as Prisma.InputJsonValue | undefined, - }), - ...(data.plan !== undefined && { - plan: data.plan as Prisma.InputJsonValue | undefined, - }), - updated_by: data.updated_by, - updated_at: new Date(), - }, - }); + // 동적 UPDATE 쿼리 생성 + const updateFields: string[] = ["updated_by = $1", "updated_at = NOW()"]; + const values: any[] = [data.updated_by]; + let paramIndex = 2; + + if (data.diagram_name) { + updateFields.push(`diagram_name = $${paramIndex++}`); + values.push(data.diagram_name); + } + if (data.relationships) { + updateFields.push(`relationships = $${paramIndex++}`); + values.push(JSON.stringify(data.relationships)); + } + if (data.node_positions !== undefined) { + updateFields.push(`node_positions = $${paramIndex++}`); + values.push( + data.node_positions ? JSON.stringify(data.node_positions) : null + ); + } + if (data.category !== undefined) { + updateFields.push(`category = $${paramIndex++}`); + values.push(data.category ? JSON.stringify(data.category) : null); + } + if (data.control !== undefined) { + updateFields.push(`control = $${paramIndex++}`); + values.push(data.control ? JSON.stringify(data.control) : null); + } + if (data.plan !== undefined) { + updateFields.push(`plan = $${paramIndex++}`); + values.push(data.plan ? JSON.stringify(data.plan) : null); + } + + const updatedDiagram = await queryOne( + `UPDATE dataflow_diagrams + SET ${updateFields.join(", ")} + WHERE diagram_id = $${paramIndex} + RETURNING *`, + [...values, diagramId] + ); return updatedDiagram; } catch (error) { @@ -248,32 +254,27 @@ export const deleteDataflowDiagram = async ( ) => { try { // 먼저 해당 관계도가 존재하는지 확인 - const whereClause: { - diagram_id: number; - company_code?: string; - } = { - diagram_id: diagramId, - }; + const whereConditions: string[] = ["diagram_id = $1"]; + const values: any[] = [diagramId]; - // company_code가 '*'가 아닌 경우에만 필터링 if (companyCode !== "*") { - whereClause.company_code = companyCode; + whereConditions.push("company_code = $2"); + values.push(companyCode); } - const existingDiagram = await prisma.dataflow_diagrams.findFirst({ - where: whereClause, - }); + const existingDiagram = await queryOne( + `SELECT * FROM dataflow_diagrams WHERE ${whereConditions.join(" AND ")} LIMIT 1`, + values + ); if (!existingDiagram) { return false; } // 삭제 실행 - await prisma.dataflow_diagrams.delete({ - where: { - diagram_id: diagramId, - }, - }); + await query(`DELETE FROM dataflow_diagrams WHERE diagram_id = $1`, [ + diagramId, + ]); return true; } catch (error) { @@ -293,21 +294,18 @@ export const copyDataflowDiagram = async ( ) => { try { // 원본 관계도 조회 - const whereClause: { - diagram_id: number; - company_code?: string; - } = { - diagram_id: diagramId, - }; + const whereConditions: string[] = ["diagram_id = $1"]; + const values: any[] = [diagramId]; - // company_code가 '*'가 아닌 경우에만 필터링 if (companyCode !== "*") { - whereClause.company_code = companyCode; + whereConditions.push("company_code = $2"); + values.push(companyCode); } - const originalDiagram = await prisma.dataflow_diagrams.findFirst({ - where: whereClause, - }); + const originalDiagram = await queryOne( + `SELECT * FROM dataflow_diagrams WHERE ${whereConditions.join(" AND ")} LIMIT 1`, + values + ); if (!originalDiagram) { return null; @@ -325,28 +323,19 @@ export const copyDataflowDiagram = async ( : originalDiagram.diagram_name; // 같은 패턴의 이름들을 찾아서 가장 큰 번호 찾기 - const copyWhereClause: { - diagram_name: { - startsWith: string; - }; - company_code?: string; - } = { - diagram_name: { - startsWith: baseName, - }, - }; + const copyWhereConditions: string[] = ["diagram_name LIKE $1"]; + const copyValues: any[] = [`${baseName}%`]; - // company_code가 '*'가 아닌 경우에만 필터링 if (companyCode !== "*") { - copyWhereClause.company_code = companyCode; + copyWhereConditions.push("company_code = $2"); + copyValues.push(companyCode); } - const existingCopies = await prisma.dataflow_diagrams.findMany({ - where: copyWhereClause, - select: { - diagram_name: true, - }, - }); + const existingCopies = await query<{ diagram_name: string }>( + `SELECT diagram_name FROM dataflow_diagrams + WHERE ${copyWhereConditions.join(" AND ")}`, + copyValues + ); let maxNumber = 0; existingCopies.forEach((copy) => { @@ -363,19 +352,24 @@ export const copyDataflowDiagram = async ( } // 새로운 관계도 생성 - const copiedDiagram = await prisma.dataflow_diagrams.create({ - data: { - diagram_name: copyName, - relationships: originalDiagram.relationships as Prisma.InputJsonValue, - node_positions: originalDiagram.node_positions - ? (originalDiagram.node_positions as Prisma.InputJsonValue) - : Prisma.JsonNull, - category: originalDiagram.category || undefined, - company_code: companyCode, - created_by: userId, - updated_by: userId, - }, - }); + const copiedDiagram = await queryOne( + `INSERT INTO dataflow_diagrams + (diagram_name, relationships, node_positions, category, company_code, + created_by, updated_by, created_at, updated_at) + VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW()) + RETURNING *`, + [ + copyName, + JSON.stringify(originalDiagram.relationships), + originalDiagram.node_positions + ? JSON.stringify(originalDiagram.node_positions) + : null, + originalDiagram.category || null, + companyCode, + userId, + userId, + ] + ); return copiedDiagram; } catch (error) { @@ -390,39 +384,39 @@ export const copyDataflowDiagram = async ( */ export const getAllRelationshipsForButtonControl = async ( companyCode: string -): Promise> => { +): Promise< + Array<{ + id: string; + name: string; + sourceTable: string; + targetTable: string; + category: string; + }> +> => { try { logger.info(`전체 관계 목록 조회 시작 - companyCode: ${companyCode}`); // dataflow_diagrams 테이블에서 관계도들을 조회 - const diagrams = await prisma.dataflow_diagrams.findMany({ - where: { - company_code: companyCode, - }, - select: { - diagram_id: true, - diagram_name: true, - relationships: true, - }, - orderBy: { - updated_at: "desc", - }, - }); + const diagrams = await query<{ + diagram_id: number; + diagram_name: string; + relationships: any; + }>( + `SELECT diagram_id, diagram_name, relationships + FROM dataflow_diagrams + WHERE company_code = $1 + ORDER BY updated_at DESC`, + [companyCode] + ); const allRelationships = diagrams.map((diagram) => { // relationships 구조에서 테이블 정보 추출 - const relationships = diagram.relationships as any || {}; - + const relationships = (diagram.relationships as any) || {}; + // 테이블 정보 추출 let sourceTable = ""; let targetTable = ""; - + if (relationships.fromTable?.tableName) { sourceTable = relationships.fromTable.tableName; }