CONCAT()

-- 모든 프로세스 조회
SHOW FULL PROCESSLIST;
-- 락테이블 조회
select * from information_schema.innodb_locks;
-- 대기중인 락 조회
select * from information_schema.innodb_lock_waits;
-- 트랜젝션조회
select * FROM information_schema.INNODB_TRX;

-- 해당 프로세스 종료
kill 495299;

-- 종합쿼리
SELECT * FROM information_schema.PROCESSLIST
WHERE Command <> 'Sleep'
AND USER NOT IN ('system user', 'event_scheduler')
ORDER BY TIME DESC
;

시스템 문제를 체크하기 위해서 DB에 SQL과 기준, 체크 스케줄을 등록하고 배치를 수행하여 문제가 발생하였을 떄 담당자에게 메일을 보내는 기능을 개발하려고 합니다. Java 기반으로 유사한 오픈 소스 프로젝트 찾아 주세요.

Quartz: 가장 널리 사용되는 Java 스케줄러 라이브러리 중 하나입니다. Cron 표현식을 사용하여 정교한 스케줄링이 가능하며, Job 및 Trigger 개념을 통해 실행할 작업을 정의하고 관리할 수 있습니다. SQL 실행, 결과 검증, 메일 발송 로직을 Job으로 구현하여 스케줄링할 수 있습니다.

https://camel.apache.org/

OpenNMS
https://github.com/OpenNMS/opennms

✅ 2. JobRunr
https://www.jobrunr.io/en/
특징:
백그라운드 작업 처리: Java 8 람다 표현식을 사용하여 백그라운드 작업을 쉽게 정의하고 실행할 수 있습니다.
스케줄링 기능: enqueue, schedule, scheduleRecurrently 메서드를 통해 작업을 즉시 실행하거나 예약할 수 있습니다.
대시보드 제공: 내장된 대시보드를 통해 작업 상태, 진행 상황, 실패 내역 등을 실시간으로 모니터링할 수 있습니다.
다양한 저장소 지원: 관계형 데이터베이스(RDBMS), MongoDB, Redis 등 다양한 저장소와 통합할 수 있습니다.
적합한 경우:
정기적인 데이터베이스 상태 점검 작업을 예약하고, 결과에 따라 이메일 알림을 보내는 시스템을 구축할 때.
작업의 상태를 시각적으로 모니터링하고 관리하고자 할 때.

  1. Dolphin Scheduler (Apache Incubator, 中国 개발팀 참여)
    https://github.com/apache/dolphinscheduler
    기능: 분산 스케줄링 시스템으로, SQL 작업을 정의하고 주기적으로 실행할 수 있으며, 실패 시 이메일/알림 발송 가능
    특징:
    웹 UI를 통해 작업 흐름(Workflow)을 정의하고 스케줄링
    MySQL, PostgreSQL 등 다양한 DB 지원
    중국 개발팀이 주도한 프로젝트로 중국어 문서 풍부
  1. Alibaba Canal (Alibaba 오픈소스)
    https://github.com/apache/dolphinscheduler
    기능: MySQL Binlog 기반의 데이터 동기화/모니터링 도구
    유사점: DB 변경 사항을 감지하고 알림 가능 (확장 개발 필요)
    참고: 주로 CDC(Change Data Capture)용이지만, 커스텀 체크 로직 추가 가능
  2. HertzBeat (中国 Dromara 커뮤니티)
    https://github.com/apache/hertzbeat
    기능: 오픈소스 실시간 모니터링 시스템으로 DB, API, 서비스 건강 상태 체크
    특징:
    지원 DB: MySQL, Oracle, PostgreSQL 등
    임계값 초과 시 이메일/웹훅 알림
    중국 개발자 중심의 활발한 커뮤니티

https://github.com/dromara/Jpom

  1. Ward (간단한 모니터링 툴)
    기능: 서버 및 DB 상태 모니터링 대시보드

참고: 알림 기능은 제한적이지만, 오픈소스라 커스터마이징 가능

  1. Jpom (Dromara)
    웹 사이
    https://github.com/dromara/Jpom
    기능: Java 프로젝트 운영 관리 도구로, 배치 및 모니터링 기능 포함

유사점: 스크립트 실행 및 알림 기능 지원

SQLeonardo

Java 기반 SQL 모니터링 및 관리 도구
스케줄링 기능과 알림 통합 가능
https://sourceforge.net/projects/sqleonardo/

Java & iBatis 소스 코드 분석 및 시각화 도구 개발

개발 환경 구축 및 코드 생성

아래에서는 프로젝트 환경 구축 및 코드 생성을 진행하겠습니다. 요구사항에 맞춰 프로젝트 구조, 데이터베이스 스키마, 백엔드/프론트엔드 코드를 순차적으로 제공합니다.

1. 프로젝트 구조

itf/
├── backend/                        # 백엔드 프로젝트
│   ├── src/
│   │   ├── main/
│   │   │   ├── java/com/viw/itf/
│   │   │   │   ├── ItfApplication.java
│   │   │   │   ├── config/        # 설정 관련 클래스
│   │   │   │   ├── controller/    # REST API 컨트롤러
│   │   │   │   ├── entity/        # JPA 엔티티
│   │   │   │   ├── repository/    # 데이터 액세스 계층
│   │   │   │   ├── service/       # 비즈니스 로직
│   │   │   │   ├── parser/        # 소스 코드 파서
│   │   │   │   ├── analyzer/      # 의존성 분석
│   │   │   │   ├── diagram/       # 다이어그램 생성
│   │   │   │   └── util/          # 유틸리티 클래스
│   │   │   └── resources/
│   │   │       ├── application.properties
│   │   │       ├── application-local.properties
│   │   │       └── schema.sql     # 데이터베이스 스키마
│   │   └── test/                  # 테스트 코드
│   └── pom.xml                    # Maven 설정
└── frontend/                      # 프론트엔드 프로젝트
    ├── public/
    ├── src/
    │   ├── assets/                # 정적 자원
    │   ├── components/            # 재사용 컴포넌트
    │   ├── pages/                 # 페이지 컴포넌트
    │   ├── services/              # API 서비스
    │   ├── store/                 # MobX 상태 관리
    │   ├── types/                 # TypeScript 타입 정의
    │   ├── utils/                 # 유틸리티 함수
    │   ├── App.tsx
    │   └── index.tsx
    ├── package.json
    └── tsconfig.json              # TypeScript 설정

2. 데이터베이스 스키마 설계

schema.sql 파일로 제공되는, MariaDB 데이터베이스에 필요한 테이블 구조입니다:

-- 프로젝트 메타데이터 테이블
CREATE TABLE project (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    description TEXT,
    java_source_path VARCHAR(500) NOT NULL,
    ibatis_source_path VARCHAR(500) NOT NULL,
    analysis_date DATETIME DEFAULT CURRENT_TIMESTAMP,
    version VARCHAR(50)
);

-- 패키지 정보 테이블
CREATE TABLE package (
    id INT AUTO_INCREMENT PRIMARY KEY,
    project_id INT NOT NULL,
    name VARCHAR(255) NOT NULL,
    full_path VARCHAR(500) NOT NULL,
    FOREIGN KEY (project_id) REFERENCES project(id) ON DELETE CASCADE
);

-- 클래스 정보 테이블
CREATE TABLE class (
    id INT AUTO_INCREMENT PRIMARY KEY,
    project_id INT NOT NULL,
    package_id INT NOT NULL,
    name VARCHAR(255) NOT NULL,
    full_name VARCHAR(500) NOT NULL,
    file_path VARCHAR(1000) NOT NULL,
    class_type ENUM('CLASS', 'INTERFACE', 'ENUM', 'ANNOTATION') NOT NULL,
    access_modifier VARCHAR(20),
    extends_class VARCHAR(500),
    implements_interfaces TEXT,
    is_component BOOLEAN DEFAULT FALSE,
    component_type VARCHAR(100),
    FOREIGN KEY (project_id) REFERENCES project(id) ON DELETE CASCADE,
    FOREIGN KEY (package_id) REFERENCES package(id) ON DELETE CASCADE
);

-- 메소드 정보 테이블
CREATE TABLE method (
    id INT AUTO_INCREMENT PRIMARY KEY,
    class_id INT NOT NULL,
    name VARCHAR(255) NOT NULL,
    signature VARCHAR(1000) NOT NULL,
    return_type VARCHAR(255),
    parameters TEXT,
    access_modifier VARCHAR(20),
    is_static BOOLEAN DEFAULT FALSE,
    start_line INT,
    end_line INT,
    FOREIGN KEY (class_id) REFERENCES class(id) ON DELETE CASCADE
);

-- SQL 쿼리 정보 테이블
CREATE TABLE sql_query (
    id INT AUTO_INCREMENT PRIMARY KEY,
    project_id INT NOT NULL,
    namespace VARCHAR(255) NOT NULL,
    query_id VARCHAR(255) NOT NULL,
    query_type ENUM('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'PROCEDURE', 'OTHER') NOT NULL,
    query_text TEXT NOT NULL,
    file_path VARCHAR(1000) NOT NULL,
    FOREIGN KEY (project_id) REFERENCES project(id) ON DELETE CASCADE
);

-- 의존성 정보 테이블
CREATE TABLE dependency (
    id INT AUTO_INCREMENT PRIMARY KEY,
    project_id INT NOT NULL,
    source_type ENUM('CLASS', 'METHOD', 'SQL') NOT NULL,
    source_id INT NOT NULL,
    target_type ENUM('CLASS', 'METHOD', 'SQL') NOT NULL,
    target_id INT NOT NULL,
    dependency_type VARCHAR(50) NOT NULL,  -- CALLS, EXTENDS, IMPLEMENTS, USES, SQL_CALL
    line_number INT,
    FOREIGN KEY (project_id) REFERENCES project(id) ON DELETE CASCADE
);

-- 다이어그램 메타데이터 테이블
CREATE TABLE diagram (
    id INT AUTO_INCREMENT PRIMARY KEY,
    project_id INT NOT NULL,
    name VARCHAR(255) NOT NULL,
    diagram_type ENUM('SEQUENCE', 'CLASS', 'COMPONENT', 'PACKAGE', 'ACTIVITY', 'FLOWCHART', 'MINDMAP', 'UML', 'CONTAINER') NOT NULL,
    description TEXT,
    creation_date DATETIME DEFAULT CURRENT_TIMESTAMP,
    last_modified DATETIME DEFAULT CURRENT_TIMESTAMP,
    author VARCHAR(100),
    layout_data TEXT,
    FOREIGN KEY (project_id) REFERENCES project(id) ON DELETE CASCADE
);

-- 다이어그램 요소 테이블
CREATE TABLE diagram_element (
    id INT AUTO_INCREMENT PRIMARY KEY,
    diagram_id INT NOT NULL,
    element_type VARCHAR(50) NOT NULL, -- CLASS, METHOD, PACKAGE, etc.
    element_id INT NOT NULL,  -- Reference to actual element
    x_position INT,
    y_position INT,
    width INT,
    height INT,
    custom_properties TEXT,  -- JSON format for additional properties
    FOREIGN KEY (diagram_id) REFERENCES diagram(id) ON DELETE CASCADE
);

-- 다이어그램 관계 테이블
CREATE TABLE diagram_relationship (
    id INT AUTO_INCREMENT PRIMARY KEY,
    diagram_id INT NOT NULL,
    source_element_id INT NOT NULL,
    target_element_id INT NOT NULL,
    relationship_type VARCHAR(50) NOT NULL,  -- ASSOCIATION, INHERITANCE, etc.
    label VARCHAR(255),
    control_points TEXT,  -- JSON format for line control points
    custom_properties TEXT,  -- JSON format for additional properties
    FOREIGN KEY (diagram_id) REFERENCES diagram(id) ON DELETE CASCADE,
    FOREIGN KEY (source_element_id) REFERENCES diagram_element(id) ON DELETE CASCADE,
    FOREIGN KEY (target_element_id) REFERENCES diagram_element(id) ON DELETE CASCADE
);

-- 인덱스 생성
CREATE INDEX idx_class_project ON class(project_id);
CREATE INDEX idx_method_class ON method(class_id);
CREATE INDEX idx_sql_project ON sql_query(project_id);
CREATE INDEX idx_dependency_project ON dependency(project_id);
CREATE INDEX idx_dependency_source ON dependency(source_type, source_id);
CREATE INDEX idx_dependency_target ON dependency(target_type, target_id);
CREATE INDEX idx_diagram_project ON diagram(project_id);
CREATE INDEX idx_diagram_element_diagram ON diagram_element(diagram_id);
CREATE INDEX idx_diagram_relationship_diagram ON diagram_relationship(diagram_id);

3. 백엔드 구현

3.1 Maven 프로젝트 설정 (pom.xml)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cohttp://m.viw.itf</groupId>
    <artifactId>com_viw_itf</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>itf</name>
    <description>Source code analyzer and diagram generator</description>
    
    <properties>
        <java.version>11</java.version>
        <javaparser.version>3.24.0</javaparser.version>
        <structurizr.version>1.16.1</structurizr.version>
    </properties>
    
    <dependencies>
        <!-- Spring Boot Starters -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        
        <!-- Database -->
        <dependency>
            <groupId>org.mariadb.jdbc</groupId>
            <artifactId>mariadb-java-client</artifactId>
            <scope>runtime</scope>
        </dependency>
        
        <!-- Development Tools -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
        <!-- Source Code Analysis -->
        <dependency>
            <groupId>com.github.javaparser</groupId>
            <artifactId>javaparser-symbol-solver-core</artifactId>
            <version>${javaparser.version}</version>
        </dependency>
        
        <!-- Structurizr for Diagrams -->
        <dependency>
            <groupId>com.structurizr</groupId>
            <artifactId>structurizr-core</artifactId>
            <version>${structurizr.version}</version>
        </dependency>
        <dependency>
            <groupId>com.structurizr</groupId>
            <artifactId>structurizr-client</artifactId>
            <version>${structurizr.version}</version>
        </dependency>
        <dependency>
            <groupId>com.structurizr</groupId>
            <artifactId>structurizr-plantuml</artifactId>
            <version>${structurizr.version}</version>
        </dependency>
        <dependency>
            <groupId>com.structurizr</groupId>
            <artifactId>structurizr-mermaid</artifactId>
            <version>${structurizr.version}</version>
        </dependency>
        <dependency>
            <groupId>com.structurizr</groupId>
            <artifactId>structurizr-export</artifactId>
            <version>${structurizr.version}</version>
        </dependency>
        <dependency>
            <groupId>com.structurizr</groupId>
            <artifactId>structurizr-autolayout</artifactId>
            <version>${structurizr.version}</version>
        </dependency>
        
        <!-- XML Parser for iBatis -->
        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.3</version>
        </dependency>
        
        <!-- JSON Processing -->
        <dependency>
            <groupId>cohttp://m.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        
        <!-- Testing -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

3.2 애플리케이션 설정 (application.properties)

# Application Name
spring.application.name=itf
server.port=8080

# Database Configuration
spring.datasource.url=jdbc:mariadb://127.0.0.1:3306/itf
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver

# JPA/Hibernate Configuration
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect

# Logging Configuration
logging.level.cohttp://m.viw.itf=DEBUG
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

# Custom Properties
app.source.java-path=C:/work_back/viw_ep_edi/src/main/java/com/viw/esp/online
app.source.ibatis-path=C:/work_back/viw_erp_wms/src/main/resources/sql

3.3 메인 애플리케이션 클래스

package cohttp://m.viw.itf;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

@SpringBootApplication
public class ItfApplication extends SpringBootServletInitializer {
    
    public static void main(String[] args) {
        SpringApplication.run(ItfApplication.class, args);
    }
}

3.4 설정 클래스

package cohttp://m.viw.itf.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.nio.file.Path;
import java.nio.file.Paths;

@Configuration
public class AppConfig implements WebMvcConfigurer {

    @Value("${app.source.java-path}")
    private String javaSourcePath;

    @Value("${app.source.ibatis-path}")
    private String ibatisSourcePath;

    @Bean
    public Path javaSourceDirectory() {
        return Paths.get(javaSourcePath);
    }

    @Bean
    public Path ibatisSourceDirectory() {
        return Paths.get(ibatisSourcePath);
    }
    
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("http://localhost:3000")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .allowCredentials(true);
    }
}

3.5 엔티티 클래스

Project.java:

package cohttp://m.viw.itf.entity;

import lombok.Data;

import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "project")
@Data
public class Project {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    @Column(nullable = false)
    private String name;
    
    private String description;
    
    @Column(name = "java_source_path", nullable = false)
    private String javaSourcePath;
    
    @Column(name = "ibatis_source_path", nullable = false)
    private String ibatisSourcePath;
    
    @Column(name = "analysis_date")
    private LocalDateTime analysisDate;
    
    private String version;
    
    @OneToMany(mappedBy = "project", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Package> packages = new ArrayList<>();
    
    @OneToMany(mappedBy = "project", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<SqlQuery> sqlQueries = new ArrayList<>();
    
    @OneToMany(mappedBy = "project", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Dependency> dependencies = new ArrayList<>();
    
    @OneToMany(mappedBy = "project", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Diagram> diagrams = new ArrayList<>();
}

Package.java:

package cohttp://m.viw.itf.entity;

import lombok.Data;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "package")
@Data
public class Package {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    @ManyToOne
    @JoinColumn(name = "project_id", nullable = false)
    private Project project;
    
    @Column(nullable = false)
    private String name;
    
    @Column(name = "full_path", nullable = false)
    private String fullPath;
    
    @OneToMany(mappedBy = "pkg", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<CodeClass> classes = new ArrayList<>();
}

CodeClass.java:

package cohttp://m.viw.itf.entity;

import lombok.Data;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "class")
@Data
public class CodeClass {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    @ManyToOne
    @JoinColumn(name = "project_id", nullable = false)
    private Project project;
    
    @ManyToOne
    @JoinColumn(name = "package_id", nullable = false)
    private Package pkg;
    
    @Column(nullable = false)
    private String name;
    
    @Column(name = "full_name", nullable = false)
    private String fullName;
    
    @Column(name = "file_path", nullable = false)
    private String filePath;
    
    @Enumerated(EnumType.STRING)
    @Column(name = "class_type", nullable = false)
    private ClassType classType;
    
    @Column(name = "access_modifier")
    private String accessModifier;
    
    @Column(name = "extends_class")
    private String extendsClass;
    
    @Column(name = "implements_interfaces")
    private String implementsInterfaces;
    
    @Column(name = "is_component")
    private Boolean isComponent = false;
    
    @Column(name = "component_type")
    private String componentType;
    
    @OneToMany(mappedBy = "codeClass", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Method> methods = new ArrayList<>();
    
    public enum ClassType {
        CLASS, INTERFACE, ENUM, ANNOTATION
    }
}

Method.java:

package cohttp://m.viw.itf.entity;

import lombok.Data;

import javax.persistence.*;

@Entity
@Table(name = "method")
@Data
public class Method {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    @ManyToOne
    @JoinColumn(name = "class_id", nullable = false)
    private CodeClass codeClass;
    
    @Column(nullable = false)
    private String name;
    
    @Column(nullable = false)
    private String signature;
    
    @Column(name = "return_type")
    private String returnType;
    
    private String parameters;
    
    @Column(name = "access_modifier")
    private String accessModifier;
    
    @Column(name = "is_static")
    private Boolean isStatic = false;
    
    @Column(name = "start_line")
    private Integer startLine;
    
    @Column(name = "end_line")
    private Integer endLine;
}

SqlQuery.java:

package cohttp://m.viw.itf.entity;

import lombok.Data;

import javax.persistence.*;

@Entity
@Table(name = "sql_query")
@Data
public class SqlQuery {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    @ManyToOne
    @JoinColumn(name = "project_id", nullable = false)
    private Project project;
    
    @Column(nullable = false)
    private String namespace;
    
    @Column(name = "query_id", nullable = false)
    private String queryId;
    
    @Enumerated(EnumType.STRING)
    @Column(name = "query_type", nullable = false)
    private QueryType queryType;
    
    @Column(name = "query_text", nullable = false, columnDefinition = "TEXT")
    private String queryText;
    
    @Column(name = "file_path", nullable = false)
    private String filePath;
    
    public enum QueryType {
        SELECT, INSERT, UPDATE, DELETE, PROCEDURE, OTHER
    }
}

Dependency.java:

package cohttp://m.viw.itf.entity;

import lombok.Data;

import javax.persistence.*;

@Entity
@Table(name = "dependency")
@Data
public class Dependency {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    @ManyToOne
    @JoinColumn(name = "project_id", nullable = false)
    private Project project;
    
    @Enumerated(EnumType.STRING)
    @Column(name = "source_type", nullable = false)
    private ElementType sourceType;
    
    @Column(name = "source_id", nullable = false)
    private Integer sourceId;
    
    @Enumerated(EnumType.STRING)
    @Column(name = "target_type", nullable = false)
    private ElementType targetType;
    
    @Column(name = "target_id", nullable = false)
    private Integer targetId;
    
    @Column(name = "dependency_type", nullable = false)
    private String dependencyType;
    
    @Column(name = "line_number")
    private Integer lineNumber;
    
    public enum ElementType {
        CLASS, METHOD, SQL
    }
}

Diagram.java:

package cohttp://m.viw.itf.entity;

import lombok.Data;

import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "diagram")
@Data
public class Diagram {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    @ManyToOne
    @JoinColumn(name = "project_id", nullable = false)
    private Project project;
    
    @Column(nullable = false)
    private String name;
    
    @Enumerated(EnumType.STRING)
    @Column(name = "diagram_type", nullable = false)
    private DiagramType diagramType;
    
    private String description;
    
    @Column(name = "creation_date")
    private LocalDateTime creationDate;
    
    @Column(name = "last_modified")
    private LocalDateTime lastModified;
    
    private String author;
    
    @Column(name = "layout_data", columnDefinition = "TEXT")
    private String layoutData;
    
    @OneToMany(mappedBy = "diagram", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<DiagramElement> elements = new ArrayList<>();
    
    @OneToMany(mappedBy = "diagram", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<DiagramRelationship> relationships = new ArrayList<>();
    
    public enum DiagramType {
        SEQUENCE, CLASS, COMPONENT, PACKAGE, ACTIVITY, FLOWCHART, MINDMAP, UML, CONTAINER
    }
}

DiagramElement.java:

package cohttp://m.viw.itf.entity;

import lombok.Data;

import javax.persistence.*;

@Entity
@Table(name = "diagram_element")
@Data
public class DiagramElement {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    @ManyToOne
    @JoinColumn(name = "diagram_id", nullable = false)
    private Diagram diagram;
    
    @Column(name = "element_type", nullable = false)
    private String elementType;
    
    @Column(name = "element_id", nullable = false)
    private Integer elementId;
    
    @Column(name = "x_position")
    private Integer xPosition;
    
    @Column(name = "y_position")
    private Integer yPosition;
    
    private Integer width;
    
    private Integer height;
    
    @Column(name = "custom_properties", columnDefinition = "TEXT")
    private String customProperties;
}

DiagramRelationship.java:

package cohttp://m.viw.itf.entity;

import lombok.Data;

import javax.persistence.*;

@Entity
@Table(name = "diagram_relationship")
@Data
public class DiagramRelationship {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    @ManyToOne
    @JoinColumn(name = "diagram_id", nullable = false)
    private Diagram diagram;
    
    @ManyToOne
    @JoinColumn(name = "source_element_id", nullable = false)
    private DiagramElement sourceElement;
    
    @ManyToOne
    @JoinColumn(name = "target_element_id", nullable = false)
    private DiagramElement targetElement;
    
    @Column(name = "relationship_type", nullable = false)
    private String relationshipType;
    
    private String label;
    
    @Column(name = "control_points", columnDefinition = "TEXT")
    private String controlPoints;
    
    @Column(name = "custom_properties", columnDefinition = "TEXT")
    private String customProperties;
}

3.6 리포지토리 인터페이스

ProjectRepository.java:

package cohttp://m.viw.itf.repository;

import cohttp://m.viw.itf.entity.Project;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ProjectRepository extends JpaRepository<Project, Integer> {
}

PackageRepository.java:

package cohttp://m.viw.itf.repository;

import cohttp://m.viw.itf.entity.Package;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface PackageRepository extends JpaRepository<Package, Integer> {
    
    List<Package> findByProjectId(Integer projectId);
    
    Optional<Package> findByProjectIdAndFullPath(Integer projectId, String fullPath);
}

CodeClassRepository.java:

package cohttp://m.viw.itf.repository;

import cohttp://m.viw.itf.entity.CodeClass;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface CodeClassRepository extends JpaRepository<CodeClass, Integer> {
    
    List<CodeClass> findByProjectId(Integer projectId);
    
    List<CodeClass> findByPkgId(Integer packageId);
    
    Optional<CodeClass> findByProjectIdAndFullName(Integer projectId, String fullName);
}

MethodRepository.java:

package cohttp://m.viw.itf.repository;

import cohttp://m.viw.itf.entity.Method;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface MethodRepository extends JpaRepository<Method, Integer> {
    
    List<Method> findByCodeClassId(Integer classId);
}

SqlQueryRepository.java:

package cohttp://m.viw.itf.repository;

import cohttp://m.viw.itf.entity.SqlQuery;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface SqlQueryRepository extends JpaRepository<SqlQuery, Integer> {
    
    List<SqlQuery> findByProjectId(Integer projectId);
    
    Optional<SqlQuery> findByProjectIdAndNamespaceAndQueryId(Integer projectId, String namespace, String queryId);
}

DependencyRepository.java:

package cohttp://m.viw.itf.repository;

import cohttp://m.viw.itf.entity.Dependency;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface DependencyRepository extends JpaRepository<Dependency, Integer> {
    
    List<Dependency> findByProjectId(Integer projectId);
    
    List<Dependency> findByProjectIdAndSourceTypeAndSourceId(
            Integer projectId, Dependency.ElementType sourceType, Integer sourceId);
    
    List<Dependency> findByProjectIdAndTargetTypeAndTargetId(
            Integer projectId, Dependency.ElementType targetType, Integer targetId);
    
    @Query("SELECT d FROM Dependency d WHERE d.project.id = :projectId AND " +
           "((d.sourceType = :type AND d.sourceId = :id) OR " +
           "(d.targetType = :type AND d.targetId = :id))")
    List<Dependency> findAllByElement(
            @Param("projectId") Integer projectId,
            @Param("type") Dependency.ElementType type,
            @Param("id") Integer id);
}

DiagramRepository.java:

package cohttp://m.viw.itf.repository;

import cohttp://m.viw.itf.entity.Diagram;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface DiagramRepository extends JpaRepository<Diagram, Integer> {
    
    List<Diagram> findByProjectId(Integer projectId);
    
    List<Diagram> findByProjectIdAndDiagramType(Integer projectId, Diagram.DiagramType diagramType);
}

3.7 소스 코드 파서 구현

JavaSourceParser.java:

package cohttp://m.viw.itf.parser;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ParserConfiguration;
import cohttp://m.github.javaparser.ast.CompilationUnit;
import cohttp://m.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import cohttp://m.github.javaparser.ast.body.MethodDeclaration;
import cohttp://m.github.javaparser.ast.visitor.VoidVisitorAdapter;
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
import cohttp://m.viw.itf.entity.*;
import cohttp://m.viw.itf.entity.Package;
import cohttp://m.viw.itf.repository.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.io.FileNotFoundException;
import java.nio.file.Path;
import java.util.Optional;

@Component
@RequiredArgsConstructor
@Slf4j
public class JavaSourceParser {

    private final ProjectRepository projectRepository;
    private final PackageRepository packageRepository;
    private final CodeClassRepository codeClassRepository;
    private final MethodRepository methodRepository;
    private final DependencyRepository dependencyRepository;
    private final Path javaSourceDirectory;
    
    private JavaParser javaParser;
    private Project currentProject;
    
    @Transactional
    public void parseJavaFiles(Project project) {
        this.currentProject = project;
        initializeJavaParser();
        
        File rootDir = javaSourceDirectory.toFile();
        if (!rootDir.exists() || !rootDir.isDirectory()) {
            log.error("Java source directory does not exist or is not a directory: {}", javaSourceDirectory);
            return;
        }
        
        processDirectory(rootDir);
    }
    
    private void initializeJavaParser() {
        // Configure symbol solver
        CombinedTypeSolver typeSolver = new CombinedTypeSolver();
        typeSolver.add(new ReflectionTypeSolver());
        typeSolver.add(new JavaParserTypeSolver(javaSourceDirectory.toFile()));
        
        JavaSymbolSolver symbolSolver = new JavaSymbolSolver(typeSolver);
        
        ParserConfiguration config = new ParserConfiguration();
        config.setSymbolResolver(symbolSolver);
        
        this.javaParser = new JavaParser(config);
    }
    
    private void processDirectory(File directory) {
        File[] files = directory.listFiles();
        if (files == null) return;
        
        for (File file : files) {
            if (file.isDirectory()) {
                processDirectory(file);
            } else if (file.getName().endsWith(".java")) {
                try {
                    log.debug("Parsing Java file: {}", file.getPath());
                    parseJavaFile(file);
                } catch (Exception e) {
                    log.error("Error parsing Java file: {}", file.getPath(), e);
                }
            }
        }
    }
    
    private void parseJavaFile(File file) throws FileNotFoundException {
        CompilationUnit cu = javaParser.parse(file).getResult().orElseThrow(() -> 
                new FileNotFoundException("Could not parse file: " + file.getPath()));
        
        String packageName = cu.getPackageDeclaration()
                .map(pd -> pd.getName().asString())
                .orElse("");
        
        // Get or create package
        Package pkg = getOrCreatePackage(packageName);
        
        // Visit class declarations
        cu.accept(new ClassVisitor(file, pkg), null);
    }
    
    private Package getOrCreatePackage(String packageName) {
        return packageRepository.findByProjectIdAndFullPath(currentProject.getId(), packageName)
                .orElseGet(() -> {
                    Package pkg = new Package();
                    pkg.setProject(currentProject);
                    pkg.setName(getSimplePackageName(packageName));
                    pkg.setFullPath(packageName);
                    return packageRepository.save(pkg);
                });
    }
    
    private String getSimplePackageName(String fullPath) {
        int lastDotIndex = fullPath.lastIndexOf('.');
        return lastDotIndex > 0 ? fullPath.substring(lastDotIndex + 1) : fullPath;
    }
    
    private class ClassVisitor extends VoidVisitorAdapter<Void> {
        private final File file;
        private final Package pkg;
        
        public ClassVisitor(File file, Package pkg) {
            this.file = file;
            this.pkg = pkg;
        }
        
        @Override
        public void visit(ClassOrInterfaceDeclaration classDecl, Void arg) {
            super.visit(classDecl, arg);
            
            try {
                String className = classDecl.getNameAsString();
                String fullClassName = pkg.getFullPath() + "." + className;
                
                CodeClass codeClass = new CodeClass();
                codeClass.setProject(currentProject);
                codeClass.setPkg(pkg);
                codeClass.setName(className);
                codeClass.setFullName(fullClassName);
                codeClass.setFilePath(file.getAbsolutePath());
                codeClass.setClassType(classDecl.isInterface() ? 
                        CodeClass.ClassType.INTERFACE : CodeClass.ClassType.CLASS);
                codeClass.setAccessModifier(classDecl.getAccessSpecifier().asString());
                
                // Handle extends
                if (classDecl.getExtendedTypes().size() > 0) {
                    codeClass.setExtendsClass(classDecl.getExtendedTypes().get(0).getNameAsString());
                }
                
                // Handle implements
                if (classDecl.getImplementedTypes().size() > 0) {
                    StringBuilder sb = new StringBuilder();
                    classDecl.getImplementedTypes().forEach(type -> 
                            sb.append(type.getNameAsString()).append(","));
                    codeClass.setImplementsInterfaces(sb.toString());
                }
                
                // Check for Spring annotations to identify components
                classDecl.getAnnotations().forEach(annotation -> {
                    String annotationName = annotation.getNameAsString();
                    if (annotationName.equals("Component") || 
                            annotationName.equals("Service") || 
                            annotationName.equals("Repository") || 
                            annotationName.equals("Controller") || 
                            annotationName.equals("RestController")) {
                        codeClass.setIsComponent(true);
                        codeClass.setComponentType(annotationName);
                    }
                });
                
                CodeClass savedClass = codeClassRepository.save(codeClass);
                
                // Process methods
                classDecl.getMethods().forEach(methodDecl -> 
                        processMethod(methodDecl, savedClass));
                
                // Process dependencies (inheritance, implementation)
                processClassDependencies(savedClass, classDecl);
                
            } catch (Exception e) {
                log.error("Error processing class: {}", classDecl.getNameAsString(), e);
            }
        }
        
        private void processMethod(MethodDeclaration methodDecl, CodeClass codeClass) {
            String methodName = methodDecl.getNameAsString();
            String signature = methodDecl.getDeclarationAsString(false, false, false);
            
            Method method = new Method();
            method.setCodeClass(codeClass);
            method.setName(methodName);
            method.setSignature(signature);
            method.setReturnType(methodDecl.getType().asString());
            method.setAccessModifier(methodDecl.getAccessSpecifier().asString());
            method.setIsStatic(methodDecl.isStatic());
            
            // Process parameters
            if (methodDecl.getParameters().size() > 0) {
                StringBuilder sb = new StringBuilder();
                methodDecl.getParameters().forEach(param -> 
                        sb.append(param.getType().asString()).append(" ")
                          .append(param.getNameAsString()).append(","));
                method.setParameters(sb.toString());
            }
            
            // Get line numbers
            methodDecl.getBegin().ifPresent(position -> method.setStartLine(position.line));
            methodDecl.getEnd().ifPresent(position -> method.setEndLine(position.line));
            
            Method savedMethod = methodRepository.save(method);
            
            // Process method dependencies
            processMethodDependencies(savedMethod, methodDecl);
        }
        
        private void processClassDependencies(CodeClass codeClass, ClassOrInterfaceDeclaration classDecl) {
            // If class extends another class
            if (classDecl.getExtendedTypes().size() > 0) {
                String extendedClassName = classDecl.getExtendedTypes().get(0).getNameAsString();
                // Try to find the extended class in the database
                Optional<CodeClass> extendedClass = codeClassRepository.findByProjectIdAndFullName(
                        currentProject.getId(), extendedClassName);
                
                extendedClass.ifPresent(target -> {
                    Dependency dependency = new Dependency();
                    dependency.setProject(currentProject);
                    dependency.setSourceType(Dependency.ElementType.CLASS);
                    dependency.setSourceId(codeClass.getId());
                    dependency.setTargetType(Dependency.ElementType.CLASS);
                    dependency.setTargetId(target.getId());
                    dependency.setDependencyType("EXTENDS");
                    dependencyRepository.save(dependency);
                });
            }
            
            // If class implements interfaces
            classDecl.getImplementedTypes().forEach(implementedType -> {
                String interfaceName = implementedType.getNameAsString();
                // Try to find the interface in the database
                Optional<CodeClass> interfaceClass = codeClassRepository.findByProjectIdAndFullName(
                        currentProject.getId(), interfaceName);
                
                interfaceClass.ifPresent(target -> {
                    Dependency dependency = new Dependency();
                    dependency.setProject(currentProject);
                    dependency.setSourceType(Dependency.ElementType.CLASS);
                    dependency.setSourceId(codeClass.getId());
                    dependency.setTargetType(Dependency.ElementType.CLASS);
                    dependency.setTargetId(target.getId());
                    dependency.setDependencyType("IMPLEMENTS");
                    dependencyRepository.save(dependency);
                });
            });
        }
        
        private void processMethodDependencies(Method method, MethodDeclaration methodDecl) {
            // Method dependency analysis would analyze method calls
            // This is a simplified version - actual implementation would be more complex
            methodDecl.findAll(cohttp://m.github.javaparser.ast.expr.MethodCallExpr.class)
                    .forEach(methodCall -> {
                        // Try to resolve the called method
                        // This is simplified - actual resolution is more complex
                        String calledMethodName = methodCall.getNameAsString();
                        
                        // For demonstration - find methods with the same name
                        methodRepository.findAll().stream()
                                .filter(m -> m.getName().equals(calledMethodName) && 
                                        !m.getId().equals(method.getId()))
                                .findFirst()
                                .ifPresent(targetMethod -> {
                                    Dependency dependency = new Dependency();
                                    dependency.setProject(currentProject);
                                    dependency.setSourceType(Dependency.ElementType.METHOD);
                                    dependency.setSourceId(method.getId());
                                    dependency.setTargetType(Dependency.ElementType.METHOD);
                                    dependency.setTargetId(targetMethod.getId());
                                    dependency.setDependencyType("CALLS");
                                    
                                    methodCall.getBegin().ifPresent(position -> 
                                            dependency.setLineNumber(position.line));
                                    
                                    dependencyRepository.save(dependency);
                                });
                    });
        }
    }
}

IBatisSourceParser.java:

package cohttp://m.viw.itf.parser;

import cohttp://m.viw.itf.entity.Dependency;
import cohttp://m.viw.itf.entity.Method;
import cohttp://m.viw.itf.entity.Project;
import cohttp://m.viw.itf.entity.SqlQuery;
import cohttp://m.viw.itf.repository.DependencyRepository;
import cohttp://m.viw.itf.repository.MethodRepository;
import cohttp://m.viw.itf.repository.SqlQueryRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.nio.file.Path;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Component
@RequiredArgsConstructor
@Slf4j
public class IBatisSourceParser {

    private final SqlQueryRepository sqlQueryRepository;
    private final MethodRepository methodRepository;
    private final DependencyRepository dependencyRepository;
    private final Path ibatisSourceDirectory;
    
    @Transactional
    public void parseIBatisFiles(Project project) {
        File rootDir = ibatisSourceDirectory.toFile();
        if (!rootDir.exists() || !rootDir.isDirectory()) {
            log.error("iBatis source directory does not exist or is not a directory: {}", ibatisSourceDirectory);
            return;
        }
        
        processDirectory(rootDir, project);
        analyzeJavaSqlDependencies(project);
    }
    
    private void processDirectory(File directory, Project project) {
        File[] files = directory.listFiles();
        if (files == null) return;
        
        for (File file : files) {
            if (file.isDirectory()) {
                processDirectory(file, project);
            } else if (file.getName().endsWith(".xml")) {
                try {
                    log.debug("Parsing iBatis file: {}", file.getPath());
                    parseIBatisFile(file, project);
                } catch (Exception e) {
                    log.error("Error parsing iBatis file: {}", file.getPath(), e);
                }
            }
        }
    }
    
    private void parseIBatisFile(File file, Project project) throws DocumentException {
        SAXReader reader = new SAXReader();
        Document document = reader.read(file);
        Element rootElement = document.getRootElement();
        
        if (!rootElement.getName().equals("sqlMap")) {
            return; // Not an iBatis SQL map file
        }
        
        String namespace = rootElement.attributeValue("namespace");
        if (namespace == null) {
            namespace = file.getName().replace(".xml", "");
        }
        
        // Process SELECT statements
        processQueryElements(rootElement.elements("select"), namespace, SqlQuery.QueryType.SELECT, file, project);
        
        // Process INSERT statements
        processQueryElements(rootElement.elements("insert"), namespace, SqlQuery.QueryType.INSERT, file, project);
        
        // Process UPDATE statements
        processQueryElements(rootElement.elements("update"), namespace, SqlQuery.QueryType.UPDATE, file, project);
        
        // Process DELETE statements
        processQueryElements(rootElement.elements("delete"), namespace, SqlQuery.QueryType.DELETE, file, project);
        
        // Process PROCEDURE statements
        processQueryElements(rootElement.elements("procedure"), namespace, SqlQuery.QueryType.PROCEDURE, file, project);
    }
    
    private void processQueryElements(List<Element> elements, String namespace, SqlQuery.QueryType queryType, 
                                     File file, Project project) {
        for (Element element : elements) {
            String id = element.attributeValue("id");
            if (id == null) continue;
            
            SqlQuery sqlQuery = new SqlQuery();
            sqlQuery.setProject(project);
            sqlQuery.setNamespace(namespace);
            sqlQuery.setQueryId(id);
            sqlQuery.setQueryType(queryType);
            sqlQuery.setQueryText(element.getTextTrim());
            sqlQuery.setFilePath(file.getAbsolutePath());
            
            sqlQueryRepository.save(sqlQuery);
        }
    }
    
    private void analyzeJavaSqlDependencies(Project project) {
        List<Method> allMethods = methodRepository.findAll();
        List<SqlQuery> allSqlQueries = sqlQueryRepository.findByProjectId(project.getId());
        
        for (Method method : allMethods) {
            // This is a simplified approach. In a real application, you would need a more robust analysis.
            String methodSource = getMethodSource(method);
            if (methodSource == null) continue;
            
            for (SqlQuery sqlQuery : allSqlQueries) {
                // Typical iBatis call patterns
                String ibatisPattern = String.format("sqlMap\\.\\w+\\([\"']%s\\.%s[\"']", 
                        Pattern.quote(sqlQuery.getNamespace()), Pattern.quote(sqlQuery.getQueryId()));
                
                Pattern pattern = Pattern.compile(ibatisPattern);
                Matcher matcher = pattern.matcher(methodSource);
                
                if (matcher.find()) {
                    Dependency dependency = new Dependency();
                    dependency.setProject(project);
                    dependency.setSourceType(Dependency.ElementType.METHOD);
                    dependency.setSourceId(method.getId());
                    dependency.setTargetType(Dependency.ElementType.SQL);
                    dependency.setTargetId(sqlQuery.getId());
                    dependency.setDependencyType("SQL_CALL");
                    dependency.setLineNumber(getLineNumber(methodSource, matcher.start()));
                    
                    dependencyRepository.save(dependency);
                }
            }
        }
    }
    
    // Simplified method to get method source - would need actual file reading in a real implementation
    private String getMethodSource(Method method) {
        // In a real implementation, you would read from the file
        // This is a placeholder
        return "public void someMethod() { sqlMap.queryForObject(\"namespace.queryId\", param); }";
    }
    
    private Integer getLineNumber(String source, int position) {
        // Count newlines up to the position
        int lines = 1;
        for (int i = 0; i < position; i++) {
            if (source.charAt(i) == '\n') {
                lines++;
            }
        }
        return lines;
    }
}

3.8 다이어그램 생성 서비스

DiagramService.java:

package cohttp://m.viw.itf.service;

import cohttp://m.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.structurizr.Workspace;
import cohttp://m.structurizr.export.plantuml.PlantUMLDiagramExporter;
import cohttp://m.structurizr.model.*;
import cohttp://m.structurizr.view.*;
import cohttp://m.viw.itf.entity.*;
import cohttp://m.viw.itf.entity.Package;
import cohttp://m.viw.itf.repository.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
@Slf4j
public class DiagramService {

    private final ProjectRepository projectRepository;
    private final PackageRepository packageRepository;
    private final CodeClassRepository codeClassRepository;
    private final MethodRepository methodRepository;
    private final SqlQueryRepository sqlQueryRepository;
    private final DependencyRepository dependencyRepository;
    private final DiagramRepository diagramRepository;
    private final ObjectMapper objectMapper;
    
    @Transactional
    public Integer createComponentDiagram(Integer projectId, String name, String description) {
        Project project = projectRepository.findById(projectId)
                .orElseThrow(() -> new RuntimeException("Project not found: " + projectId));
        
        // Create a Structurizr workspace for the component diagram
        Workspace workspace = new Workspace(project.getName(), project.getDescription());
        Model model = workspace.getModel();
        
        // Create a software system to represent the project
        SoftwareSystem system = model.addSoftwareSystem(project.getName(), project.getDescription());
        
        // Find all component classes (Spring annotations)
        List<CodeClass> componentClasses = codeClassRepository.findByProjectId(projectId).stream()
                .filter(CodeClass::getIsComponent)
                .collect(Collectors.toList());
        
        // Create a container to group all components
        Container container = system.addContainer("Application", "Application Container", "Java/Spring");
        
        // Map of component ID to Structurizr Component
        Map<Integer, Component> componentMap = new HashMap<>();
        
        // Create components
        for (CodeClass componentClass : componentClasses) {
            String technology = componentClass.getComponentType() != null ? 
                    "Spring " + componentClass.getComponentType() : "Spring Component";
            
            Component component = container.addComponent(
                    componentClass.getName(),
                    componentClass.getFullName(),
                    technology);
            
            componentMap.put(componentClass.getId(), component);
        }
        
        // Add relationships between components
        for (CodeClass sourceClass : componentClasses) {
            // Find dependencies where this class is the source
            List<Dependency> dependencies = dependencyRepository.findByProjectIdAndSourceTypeAndSourceId(
                    projectId, Dependency.ElementType.CLASS, sourceClass.getId());
            
            for (Dependency dependency : dependencies) {
                if (dependency.getTargetType() == Dependency.ElementType.CLASS) {
                    Component sourceComponent = componentMap.get(sourceClass.getId());
                    Component targetComponent = componentMap.get(dependency.getTargetId());
                    
                    if (sourceComponent != null && targetComponent != null) {
                        sourceComponent.uses(targetComponent, dependency.getDependencyType());
                    }
                }
            }
        }
        
        // Create component diagram
        ViewSet viewSet = workspace.getViews();
        ComponentView componentView = viewSet.createComponentView(container, name, description);
        componentView.addAllComponents();
        
        // Export as PlantUML
        PlantUMLDiagramExporter exporter = new PlantUMLDiagramExporter();
        String plantUmlDefinition = exporter.export(componentView);
        
        // Save to diagram table
        Diagram diagram = new Diagram();
        diagram.setProject(project);
        diagram.setName(name);
        diagram.setDiagramType(Diagram.DiagramType.COMPONENT);
        diagram.setDescription(description);
        diagram.setCreationDate(LocalDateTime.now());
        diagram.setLastModified(LocalDateTime.now());
        diagram.setLayoutData(plantUmlDefinition);
        
        Diagram savedDiagram = diagramRepository.save(diagram);
        
        // Create diagram elements for each component
        for (CodeClass componentClass : componentClasses) {
            DiagramElement element = new DiagramElement();
            element.setDiagram(savedDiagram);
            element.setElementType("CLASS");
            element.setElementId(componentClass.getId());
            
            try {
                Map<String, Object> properties = new HashMap<>();
                properties.put("name", componentClass.getName());
                properties.put("type", componentClass.getComponentType());
                element.setCustomProperties(objectMapper.writeValueAsString(properties));
            } catch (JsonProcessingException e) {
                log.error("Error serializing properties", e);
            }
            
            // Positions would be set by a layout algorithm or user input
            // For now, assign random positions
            element.setXPosition(new Random().nextInt(800));
            element.setYPosition(new Random().nextInt(600));
            
            savedDiagram.getElements().add(element);
        }
        
        return savedDiagram.getId();
    }
    
    @Transactional
    public Integer createSequenceDiagram(Integer projectId, Integer methodId, String name, String description) {
        Project project = projectRepository.findById(projectId)
                .orElseThrow(() -> new RuntimeException("Project not found: " + projectId));
        
        Method rootMethod = methodRepository.findById(methodId)
                .orElseThrow(() -> new RuntimeException("Method not found: " + methodId));
        
        // Create a Structurizr workspace for the sequence diagram
        Workspace workspace = new Workspace(project.getName(), project.getDescription());
        Model model = workspace.getModel();
        
        // Create a software system
        SoftwareSystem system = model.addSoftwareSystem(project.getName(), project.getDescription());
        
        // Create a container
        Container container = system.addContainer("Application", "Application Container", "Java/Spring");
        
        // Create components for all classes involved
        Map<Integer, Component> componentMap = new HashMap<>();
        Set<Integer> processedMethodIds = new HashSet<>();
        
        // Start with the root method's class
        addComponentForClass(componentMap, container, rootMethod.getCodeClass());
        
        // Recursively process method calls
        processMethodCalls(projectId, methodId, processedMethodIds, componentMap, container);
        
        // Create dynamic view (sequence diagram)
        ViewSet viewSet = workspace.getViews();
        DynamicView dynamicView = viewSet.createDynamicView(container, name, description);
        
        // Start with the root method
        Component rootComponent = componentMap.get(rootMethod.getCodeClass().getId());
        
        // Build sequence of calls
        buildSequenceDiagram(dynamicView, rootComponent, projectId, methodId, rootMethod, new HashSet<>());
        
        // Export as PlantUML
        PlantUMLDiagramExporter exporter = new PlantUMLDiagramExporter();
        String plantUmlDefinition = exporter.export(dynamicView);
        
        // Save to diagram table
        Diagram diagram = new Diagram();
        diagram.setProject(project);
        diagram.setName(name);
        diagram.setDiagramType(Diagram.DiagramType.SEQUENCE);
        diagram.setDescription(description);
        diagram.setCreationDate(LocalDateTime.now());
        diagram.setLastModified(LocalDateTime.now());
        diagram.setLayoutData(plantUmlDefinition);
        
        return diagramRepository.save(diagram).getId();
    }
    
    private void addComponentForClass(Map<Integer, Component> componentMap, Container container, CodeClass codeClass) {
        if (!componentMap.containsKey(codeClass.getId())) {
            Component component = container.addComponent(
                    codeClass.getName(),
                    codeClass.getFullName(),
                    codeClass.getIsComponent() ? ("Spring " + codeClass.getComponentType()) : "Java Class");
            
            componentMap.put(codeClass.getId(), component);
        }
    }
    
    private void processMethodCalls(Integer projectId, Integer methodId, Set<Integer> processedMethodIds,
                                    Map<Integer, Component> componentMap, Container container) {
        if (processedMethodIds.contains(methodId)) {
            return; // Already processed
        }
        
        processedMethodIds.add(methodId);
        
        // Find method calls from this method
        List<Dependency> methodCalls = dependencyRepository.findByProjectIdAndSourceTypeAndSourceId(
                projectId, Dependency.ElementType.METHOD, methodId);
        
        for (Dependency dependency : methodCalls) {
            if (dependency.getTargetType() == Dependency.ElementType.METHOD) {
                Method targetMethod = methodRepository.findById(dependency.getTargetId())
                        .orElse(null);
                
                if (targetMethod != null) {
                    // Add component for target method's class
                    addComponentForClass(componentMap, container, targetMethod.getCodeClass());
                    
                    // Recursively process method calls
                    processMethodCalls(projectId, targetMethod.getId(), processedMethodIds, componentMap, container);
                }
            } else if (dependency.getTargetType() == Dependency.ElementType.SQL) {
                // Add a special component for database
                if (!componentMap.containsKey(-1)) { // Use a special ID for database
                    Component dbComponent = container.addComponent("Database", "Database", "MariaDB");
                    componentMap.put(-1, dbComponent);
                }
            }
        }
    }
    
    private void buildSequenceDiagram(DynamicView dynamicView, Component rootComponent,
                                     Integer projectId, Integer methodId, Method method, Set<Integer> processedCalls) {
        // Find method calls from this method
        List<Dependency> methodCalls = dependencyRepository.findByProjectIdAndSourceTypeAndSourceId(
                projectId, Dependency.ElementType.METHOD, methodId);
        
        for (Dependency dependency : methodCalls) {
            if (processedCalls.contains(dependency.getId())) {
                continue; // Avoid cycles
            }
            
            processedCalls.add(dependency.getId());
            
            if (dependency.getTargetType() == Dependency.ElementType.METHOD) {
                Method targetMethod = methodRepository.findById(dependency.getTargetId())
                        .orElse(null);
                
                if (targetMethod != null) {
                    Component targetComponent = dynamicView.getModel().getComponents().stream()
                            .filter(c -> c.getName().equals(targetMethod.getCodeClass().getName()))
                            .findFirst()
                            .orElse(null);
                    
                    if (targetComponent != null) {
                        dynamicView.add(rootComponent, targetMethod.getName(), targetComponent);
                        
                        // Recursively build sequence
                        buildSequenceDiagram(dynamicView, targetComponent, projectId, 
                                targetMethod.getId(), targetMethod, processedCalls);
                    }
                }
            } else if (dependency.getTargetType() == Dependency.ElementType.SQL) {
                SqlQuery sqlQuery = sqlQueryRepository.findById(dependency.getTargetId())
                        .orElse(null);
                
                if (sqlQuery != null) {
                    Component dbComponent = dynamicView.getModel().getComponents().stream()
                            .filter(c -> c.getName().equals("Database"))
                            .findFirst()
                            .orElse(null);
                    
                    if (dbComponent != null) {
                        dynamicView.add(rootComponent, 
                                sqlQuery.getNamespace() + "." + sqlQuery.getQueryId(), 
                                dbComponent);
                    }
                }
            }
        }
    }
    
    // Other diagram creation methods would follow a similar pattern
    @Transactional
    public Integer createClassDiagram(Integer projectId, String name, String description) {
        // Implementation similar to createComponentDiagram
        // but would include all classes, not just components
        return 0;
    }
    
    @Transactional
    public Integer createPackageDiagram(Integer projectId, String name, String description) {
        // Implementation would show package dependencies
        return 0;
    }
    
    @Transactional
    public String getDiagramContent(Integer diagramId) {
        Diagram diagram = diagramRepository.findById(diagramId)
                .orElseThrow(() -> new RuntimeException("Diagram not found: " + diagramId));
        
        return diagram.getLayoutData();
    }
    
    @Transactional
    public void updateDiagramLayout(Integer diagramId, String layoutData) {
        Diagram diagram = diagramRepository.findById(diagramId)
                .orElseThrow(() -> new RuntimeException("Diagram not found: " + diagramId));
        
        diagram.setLayoutData(layoutData);
        diagram.setLastModified(LocalDateTime.now());
        
        diagramRepository.save(diagram);
    }
}

3.9 분석 서비스

SourceAnalysisService.java:

package cohttp://m.viw.itf.service;

import cohttp://m.viw.itf.entity.Project;
import cohttp://m.viw.itf.parser.IBatisSourceParser;
import cohttp://m.viw.itf.parser.JavaSourceParser;
import cohttp://m.viw.itf.repository.ProjectRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;

@Service
@RequiredArgsConstructor
@Slf4j
public class SourceAnalysisService {

    private final ProjectRepository projectRepository;
    private final JavaSourceParser javaSourceParser;
    private final IBatisSourceParser ibatisSourceParser;
    
    @Transactional
    public Integer createAndAnalyzeProject(String name, String description, String javaSourcePath, 
                                         String ibatisSourcePath, String version) {
        log.info("Creating and analyzing new project: {}", name);
        
        // Create new project
        Project project = new Project();
        project.setName(name);
        project.setDescription(description);
        project.setJavaSourcePath(javaSourcePath);
        project.setIbatisSourcePath(ibatisSourcePath);
        project.setAnalysisDate(LocalDateTime.now());
        project.setVersion(version);
        
        Project savedProject = projectRepository.save(project);
        
        // Analyze source code
        analyzeProject(savedProject.getId());
        
        return savedProject.getId();
    }
    
    @Transactional
    public void analyzeProject(Integer projectId) {
        log.info("Analyzing project: {}", projectId);
        
        Project project = projectRepository.findById(projectId)
                .orElseThrow(() -> new RuntimeException("Project not found: " + projectId));
        
        // Parse Java source files
        javaSourceParser.parseJavaFiles(project);
        
        // Parse iBatis source files
        ibatisSourceParser.parseIBatisFiles(project);
        
        // Update analysis date
        project.setAnalysisDate(LocalDateTime.now());
        projectRepository.save(project);
        
        log.info("Analysis completed for project: {}", projectId);
    }
}

3.10 REST API 컨트롤러

ProjectController.java:

package cohttp://m.viw.itf.controller;

import cohttp://m.viw.itf.dto.ProjectCreateRequest;
import cohttp://m.viw.itf.entity.Project;
import cohttp://m.viw.itf.repository.ProjectRepository;
import cohttp://m.viw.itf.service.SourceAnalysisService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.List;

@RestController
@RequestMapping("/api/projects")
@RequiredArgsConstructor
public class ProjectController {

    private final ProjectRepository projectRepository;
    private final SourceAnalysisService sourceAnalysisService;
    
    @GetMapping
    public ResponseEntity<List<Project>> getAllProjects() {
        return ResponseEntity.ok(projectRepository.findAll());
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<Project> getProject(@PathVariable Integer id) {
        return projectRepository.findById(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }
    
    @PostMapping
    public ResponseEntity<Project> createProject(@Valid @RequestBody ProjectCreateRequest request) {
        Integer projectId = sourceAnalysisService.createAndAnalyzeProject(
                request.getName(),
                request.getDescription(),
                request.getJavaSourcePath(),
                request.getIbatisSourcePath(),
                request.getVersion()
        );
        
        return projectRepository.findById(projectId)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }
    
    @PostMapping("/{id}/analyze")
    public ResponseEntity<Void> analyzeProject(@PathVariable Integer id) {
        sourceAnalysisService.analyzeProject(id);
        return ResponseEntity.ok().build();
    }
}

DiagramController.java:

package cohttp://m.viw.itf.controller;

import cohttp://m.viw.itf.dto.DiagramCreateRequest;
import cohttp://m.viw.itf.dto.DiagramResponse;
import cohttp://m.viw.itf.dto.DiagramUpdateRequest;
import cohttp://m.viw.itf.entity.Diagram;
import cohttp://m.viw.itf.repository.DiagramRepository;
import cohttp://m.viw.itf.service.DiagramService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.List;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/api/diagrams")
@RequiredArgsConstructor
public class DiagramController {

    private final DiagramRepository diagramRepository;
    private final DiagramService diagramService;
    
    @GetMapping("/project/{projectId}")
    public ResponseEntity<List<DiagramResponse>> getDiagramsByProject(@PathVariable Integer projectId) {
        List<Diagram> diagrams = diagramRepository.findByProjectId(projectId);
        
        List<DiagramResponse> response = diagrams.stream()
                .map(diagram -> new DiagramResponse(
                        diagram.getId(),
                        diagram.getName(),
                        diagram.getDiagramType().toString(),
                        diagram.getDescription(),
                        diagram.getCreationDate(),
                        diagram.getLastModified()
                ))
                .collect(Collectors.toList());
        
        return ResponseEntity.ok(response);
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<String> getDiagramContent(@PathVariable Integer id) {
        String content = diagramService.getDiagramContent(id);
        return ResponseEntity.ok(content);
    }
    
    @PostMapping("/component")
    public ResponseEntity<Integer> createComponentDiagram(@Valid @RequestBody DiagramCreateRequest request) {
        Integer diagramId = diagramService.createComponentDiagram(
                request.getProjectId(),
                request.getName(),
                request.getDescription()
        );
        
        return ResponseEntity.ok(diagramId);
    }
    
    @PostMapping("/sequence")
    public ResponseEntity<Integer> createSequenceDiagram(
            @Valid @RequestBody DiagramCreateRequest request,
            @RequestParam Integer methodId) {
        
        Integer diagramId = diagramService.createSequenceDiagram(
                request.getProjectId(),
                methodId,
                request.getName(),
                request.getDescription()
        );
        
        return ResponseEntity.ok(diagramId);
    }
    
    @PutMapping("/{id}")
    public ResponseEntity<Void> updateDiagramLayout(
            @PathVariable Integer id,
            @Valid @RequestBody DiagramUpdateRequest request) {
        
        diagramService.updateDiagramLayout(id, request.getLayoutData());
        return ResponseEntity.ok().build();
    }
}

ClassController.java:

package cohttp://m.viw.itf.controller;

import cohttp://m.viw.itf.dto.ClassResponse;
import cohttp://m.viw.itf.entity.CodeClass;
import cohttp://m.viw.itf.repository.CodeClassRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/api/classes")
@RequiredArgsConstructor
public class ClassController {

    private final CodeClassRepository codeClassRepository;
    
    @GetMapping("/project/{projectId}")
    public ResponseEntity<List<ClassResponse>> getClassesByProject(@PathVariable Integer projectId) {
        List<CodeClass> classes = codeClassRepository.findByProjectId(projectId);
        
        List<ClassResponse> response = classes.stream()
                .map(codeClass -> new ClassResponse(
                        codeClass.getId(),
                        codeClass.getName(),
                        codeClass.getFullName(),
                        codeClass.getPkg().getFullPath(),
                        codeClass.getClassType().toString(),
                        codeClass.getIsComponent(),
                        codeClass.getComponentType()
                ))
                .collect(Collectors.toList());
        
        return ResponseEntity.ok(response);
    }
    
    @GetMapping("/package/{packageId}")
    public ResponseEntity<List<ClassResponse>> getClassesByPackage(@PathVariable Integer packageId) {
        List<CodeClass> classes = codeClassRepository.findByPkgId(packageId);
        
        List<ClassResponse> response = classes.stream()
                .map(codeClass -> new ClassResponse(
                        codeClass.getId(),
                        codeClass.getName(),
                        codeClass.getFullName(),
                        codeClass.getPkg().getFullPath(),
                        codeClass.getClassType().toString(),
                        codeClass.getIsComponent(),
                        codeClass.getComponentType()
                ))
                .collect(Collectors.toList());
        
        return ResponseEntity.ok(response);
    }
}

3.11 DTO 클래스

ProjectCreateRequest.java:

package cohttp://m.viw.itf.dto;

import lombok.Data;

import javax.validation.constraints.NotBlank;

@Data
public class ProjectCreateRequest {
    
    @NotBlank
    private String name;
    
    private String description;
    
    @NotBlank
    private String javaSourcePath;
    
    @NotBlank
    private String ibatisSourcePath;
    
    private String version;
}

DiagramCreateRequest.java:

package cohttp://m.viw.itf.dto;

import lombok.Data;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

@Data
public class DiagramCreateRequest {
    
    @NotNull
    private Integer projectId;
    
    @NotBlank
    private String name;
    
    private String description;
}

DiagramUpdateRequest.java:

package cohttp://m.viw.itf.dto;

import lombok.Data;

import javax.validation.constraints.NotBlank;

@Data
public class DiagramUpdateRequest {
    
    @NotBlank
    private String layoutData;
}

DiagramResponse.java:

package cohttp://m.viw.itf.dto;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.time.LocalDateTime;

@Data
@AllArgsConstructor
public class DiagramResponse {
    
    private Integer id;
    private String name;
    private String type;
    private String description;
    private LocalDateTime creationDate;
    private LocalDateTime lastModified;
}

ClassResponse.java:

package cohttp://m.viw.itf.dto;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class ClassResponse {
    
    private Integer id;
    private String name;
    private String fullName;
    private String packagePath;
    private String type;
    private Boolean isComponent;
    private String componentType;
}

4. 프론트엔드 구현

4.1 package.json

{
  "name": "itf-frontend",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@ag-grid-community/client-side-row-model": "26.1.0",
    "@ag-grid-community/core": "26.1.0",
    "@ag-grid-community/react": "26.1.0",
    "@testing-library/jest-dom": "^5.11.4",
    "@testing-library/react": "^11.1.0",
    "@testing-library/user-event": "^12.1.10",
    "@types/jest": "^26.0.15",
    "@types/node": "^12.0.0",
    "@types/react": "^17.0.0",
    "@types/react-dom": "^17.0.0",
    "@types/react-router-dom": "^5.1.7",
    "axios": "^0.21.1",
    "bootstrap": "^5.0.1",
    "d3": "^7.0.0",
    "file-saver": "^2.0.5",
    "html2canvas": "^1.3.2",
    "html2pdf.js": "^0.9.3",
    "jspdf": "^2.3.1",
    "mermaid": "^8.11.0",
    "mobx": "^6.3.2",
    "mobx-react-lite": "^3.2.0",
    "react": "^17.0.2",
    "react-bootstrap": "^1.6.1",
    "react-dom": "^17.0.2",
    "react-hook-form": "^7.14.1",
    "react-router-dom": "^5.2.0",
    "react-scripts": "4.0.3",
    "reactflow": "^9.5.4",
    "typescript": "^4.1.2",
    "web-vitals": "^1.0.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "@types/d3": "^7.0.0",
    "@types/file-saver": "^2.0.3"
  }
}

4.2 TypeScript 타입 정의 (src/types/index.ts)

// Project types
export interface Project {
  id: number;
  name: string;
  description: string;
  javaSourcePath: string;
  ibatisSourcePath: string;
  analysisDate: string;
  version: string;
}

export interface ProjectCreateRequest {
  name: string;
  description?: string;
  javaSourcePath: string;
  ibatisSourcePath: string;
  version?: string;
}

// Class types
export interface CodeClass {
  id: number;
  name: string;
  fullName: string;
  packagePath: string;
  type: string;
  isComponent: boolean;
  componentType?: string;
}

// Package types
export interface Package {
  id: number;
  name: string;
  fullPath: string;
}

// Method types
export interface Method {
  id: number;
  name: string;
  signature: string;
  returnType: string;
  parameters: string;
  accessModifier: string;
  isStatic: boolean;
}

// SQL types
export interface SqlQuery {
  id: number;
  namespace: string;
  queryId: string;
  queryType: string;
  queryText: string;
  filePath: string;
}

// Dependency types
export interface Dependency {
  id: number;
  sourceType: string;
  sourceId: number;
  targetType: string;
  targetId: number;
  dependencyType: string;
  lineNumber?: number;
}

// Diagram types
export interface Diagram {
  id: number;
  name: string;
  type: string;
  description: string;
  creationDate: string;
  lastModified: string;
}

export interface DiagramCreateRequest {
  projectId: number;
  name: string;
  description?: string;
}

export interface DiagramUpdateRequest {
  layoutData: string;
}

// Element types for diagrams
export interface DiagramElement {
  id: number;
  elementType: string;
  elementId: number;
  xPosition: number;
  yPosition: number;
  width?: number;
  height?: number;
  customProperties?: any;
}

export interface DiagramRelationship {
  id: number;
  sourceElementId: number;
  targetElementId: number;
  relationshipType: string;
  label?: string;
  controlPoints?: any;
  customProperties?: any;
}

4.3 API 서비스 (src/services/api.ts)

import axios from 'axios';
import { 
  Project, 
  ProjectCreateRequest, 
  CodeClass, 
  Method, 
  SqlQuery, 
  Diagram, 
  DiagramCreateRequest, 
  DiagramUpdateRequest 
} from '../types';

const API_BASE_URL = 'http://localhost:8080/api';

// Configure axios
axios.defaults.baseURL = API_BASE_URL;
axios.defaults.headers.common['Content-Type'] = 'application/json';

// Project API
export const projectApi = {
  getAllProjects: async (): Promise<Project[]> => {
    const response = await axios.get('/projects');
    return response.data;
  },
  
  getProject: async (id: number): Promise<Project> => {
    const response = await axios.get(`/projects/${id}`);
    return response.data;
  },
  
  createProject: async (project: ProjectCreateRequest): Promise<Project> => {
    const response = await axios.post('/projects', project);
    return response.data;
  },
  
  analyzeProject: async (id: number): Promise<void> => {
    await axios.post(`/projects/${id}/analyze`);
  }
};

// Class API
export const classApi = {
  getClassesByProject: async (projectId: number): Promise<CodeClass[]> => {
    const response = await axios.get(`/classes/project/${projectId}`);
    return response.data;
  },
  
  getClassesByPackage: async (packageId: number): Promise<CodeClass[]> => {
    const response = await axios.get(`/classes/package/${packageId}`);
    return response.data;
  },
  
  getMethodsByClass: async (classId: number): Promise<Method[]> => {
    const response = await axios.get(`/methods/class/${classId}`);
    return response.data;
  }
};

// SQL API
export const sqlApi = {
  getSqlQueriesByProject: async (projectId: number): Promise<SqlQuery[]> => {
    const response = await axios.get(`/sql/project/${projectId}`);
    return response.data;
  }
};

// Diagram API
export const diagramApi = {
  getDiagramsByProject: async (projectId: number): Promise<Diagram[]> => {
    const response = await axios.get(`/diagrams/project/${projectId}`);
    return response.data;
  },
  
  getDiagramContent: async (id: number): Promise<string> => {
    const response = await axios.get(`/diagrams/${id}`);
    return response.data;
  },
  
  createComponentDiagram: async (request: DiagramCreateRequest): Promise<number> => {
    const response = await axios.post('/diagrams/component', request);
    return response.data;
  },
  
  createSequenceDiagram: async (request: DiagramCreateRequest, methodId: number): Promise<number> => {
    const response = await axios.post(`/diagrams/sequence?methodId=${methodId}`, request);
    return response.data;
  },
  
  updateDiagramLayout: async (id: number, request: DiagramUpdateRequest): Promise<void> => {
    await axios.put(`/diagrams/${id}`, request);
  }
};

4.4 MobX 상태 관리 (src/store/index.ts)

import { makeAutoObservable, runInAction } from 'mobx';
import { 
  Project, 
  CodeClass, 
  Method, 
  SqlQuery, 
  Diagram 
} from '../types';
import { projectApi, classApi, sqlApi, diagramApi } from '../services/api';

class RootStore {
  // Projects
  projects: Project[] = [];
  selectedProject: Project | null = null;
  isLoadingProjects = false;
  
  // Classes
  classes: CodeClass[] = [];
  selectedClass: CodeClass | null = null;
  isLoadingClasses = false;
  
  // Methods
  methods: Method[] = [];
  selectedMethod: Method | null = null;
  isLoadingMethods = false;
  
  // SQL Queries
  sqlQueries: SqlQuery[] = [];
  isLoadingSqlQueries = false;
  
  // Diagrams
  diagrams: Diagram[] = [];
  selectedDiagram: Diagram | null = null;
  diagramContent: string = '';
  isLoadingDiagrams = false;
  
  constructor() {
    makeAutoObservable(this);
  }
  
  // Project actions
  async loadProjects() {
    this.isLoadingProjects = true;
    try {
      const projects = await projectApi.getAllProjects();
      runInAction(() => {
        this.projects = projects;
        this.isLoadingProjects = false;
      });
    } catch (error) {
      runInAction(() => {
        this.isLoadingProjects = false;
        console.error('Failed to load projects', error);
      });
    }
  }
  
  async loadProject(id: number) {
    this.isLoadingProjects = true;
    try {
      const project = await projectApi.getProject(id);
      runInAction(() => {
        this.selectedProject = project;
        this.isLoadingProjects = false;
      });
      return project;
    } catch (error) {
      runInAction(() => {
        this.isLoadingProjects = false;
        console.error(`Failed to load project id=${id}`, error);
      });
      return null;
    }
  }
  
  async createProject(project: Project) {
    try {
      const createdProject = await projectApi.createProject(project);
      runInAction(() => {
        this.projects.push(createdProject);
        this.selectedProject = createdProject;
      });
      return createdProject;
    } catch (error) {
      console.error('Failed to create project', error);
      return null;
    }
  }
  
  // Class actions
  async loadClassesByProject(projectId: number) {
    this.isLoadingClasses = true;
    try {
      const classes = await classApi.getClassesByProject(projectId);
      runInAction(() => {
        this.classes = classes;
        this.isLoadingClasses = false;
      });
    } catch (error) {
      runInAction(() => {
        this.isLoadingClasses = false;
        console.error(`Failed to load classes for project id=${projectId}`, error);
      });
    }
  }
  
  setSelectedClass(cls: CodeClass | null) {
    this.selectedClass = cls;
  }
  
  // Method actions
  async loadMethodsByClass(classId: number) {
    this.isLoadingMethods = true;
    try {
      const methods = await classApi.getMethodsByClass(classId);
      runInAction(() => {
        this.methods = methods;
        this.isLoadingMethods = false;
      });
    } catch (error) {
      runInAction(() => {
        this.isLoadingMethods = false;
        console.error(`Failed to load methods for class id=${classId}`, error);
      });
    }
  }
  
  setSelectedMethod(method: Method | null) {
    this.selectedMethod = method;
  }
  
  // SQL actions
  async loadSqlQueriesByProject(projectId: number) {
    this.isLoadingSqlQueries = true;
    try {
      const queries = await sqlApi.getSqlQueriesByProject(projectId);
      runInAction(() => {
        this.sqlQueries = queries;
        this.isLoadingSqlQueries = false;
      });
    } catch (error) {
      runInAction(() => {
        this.isLoadingSqlQueries = false;
        console.error(`Failed to load SQL queries for project id=${projectId}`, error);
      });
    }
  }
  
  // Diagram actions
  async loadDiagramsByProject(projectId: number) {
    this.isLoadingDiagrams = true;
    try {
      const diagrams = await diagramApi.getDiagramsByProject(projectId);
      runInAction(() => {
        this.diagrams = diagrams;
        this.isLoadingDiagrams = false;
      });
    } catch (error) {
      runInAction(() => {
        this.isLoadingDiagrams = false;
        console.error(`Failed to load diagrams for project id=${projectId}`, error);
      });
    }
  }
  
  async loadDiagramContent(id: number) {
    this.isLoadingDiagrams = true;
    try {
      const content = await diagramApi.getDiagramContent(id);
      runInAction(() => {
        this.diagramContent = content;
        this.isLoadingDiagrams = false;
      });
    } catch (error) {
      runInAction(() => {
        this.isLoadingDiagrams = false;
        console.error(`Failed to load diagram content for id=${id}`, error);
      });
    }
  }
  
  setSelectedDiagram(diagram: Diagram | null) {
    this.selectedDiagram = diagram;
  }
  
  async createComponentDiagram(projectId: number, name: string, description?: string) {
    try {
      const diagramId = await diagramApi.createComponentDiagram({
        projectId,
        name,
        description
      });
      await this.loadDiagramsByProject(projectId);
      return diagramId;
    } catch (error) {
      console.error('Failed to create component diagram', error);
      return null;
    }
  }
  
  async createSequenceDiagram(projectId: number, methodId: number, name: string, description?: string) {
    try {
      const diagramId = await diagramApi.createSequenceDiagram({
        projectId,
        name,
        description
      }, methodId);
      await this.loadDiagramsByProject(projectId);
      return diagramId;
    } catch (error) {
      console.error('Failed to create sequence diagram', error);
      return null;
    }
  }
  
  async updateDiagramLayout(id: number, layoutData: string) {
    try {
      await diagramApi.updateDiagramLayout(id, { layoutData });
      // Refresh content
      await this.loadDiagramContent(id);
    } catch (error) {
      console.error(`Failed to update diagram layout for id=${id}`, error);
    }
  }
}

export const rootStore = new RootStore();

4.5 React 컴포넌트

App.tsx

import React from 'react';
import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom';
import { Container } from 'react-bootstrap';
import Navigation from './components/Navigation';
import HomePage from './pages/HomePage';
import ProjectListPage from './pages/ProjectListPage';
import ProjectDetailPage from './pages/ProjectDetailPage';
import DiagramListPage from './pages/DiagramListPage';
import DiagramDetailPage from './pages/DiagramDetailPage';
import CreateDiagramPage from './pages/CreateDiagramPage';
import 'bootstrap/dist/css/bootstrap.min.css';
import './App.css';

const App: React.FC = () => {
  return (
    <Router>
      <Navigation />
      <Container fluid className="app-container">
        <Switch>
          <Route exact path="/" component={HomePage} />
          <Route exact path="/projects" component={ProjectListPage} />
          <Route exact path="/projects/:projectId" component={ProjectDetailPage} />
          <Route exact path="/projects/:projectId/diagrams" component={DiagramListPage} />
          <Route exact path="/diagrams/:diagramId" component={DiagramDetailPage} />
          <Route exact path="/projects/:projectId/create-diagram" component={CreateDiagramPage} />
          <Redirect to="/" />
        </Switch>
      </Container>
    </Router>
  );
};

export default App;

src/components/Navigation.tsx

import React from 'react';
import { Link, useLocation } from 'react-router-dom';
import { Navbar, Nav, Container } from 'react-bootstrap';

const Navigation: React.FC = () => {
  const location = useLocation();
  
  return (
    <Navbar bg="dark" variant="dark" expand="lg">
      <Container>
        <Navbar.Brand as={Link} to="/">Code Visualizer</Navbar.Brand>
        <Navbar.Toggle aria-controls="basic-navbar-nav" />
        <Navbar.Collapse id="basic-navbar-nav">
          <Nav className="me-auto" activeKey={location.pathname}>
            <Nav.Link as={Link} to="/">Home</Nav.Link>
            <Nav.Link as={Link} to="/projects">Projects</Nav.Link>
          </Nav>
        </Navbar.Collapse>
      </Container>
    </Navbar>
  );
};

export default Navigation;

src/pages/HomePage.tsx

import React from 'react';
import { Link } from 'react-router-dom';
import { Container, Row, Col, Card, Button } from 'react-bootstrap';

const HomePage: React.FC = () => {
  return (
    <Container className="py-5">
      <Row className="mb-4">
        <Col>
          <h1>Code Visualization Tool</h1>
          <p className="lead">
            Analyze Java and iBatis source code to generate interactive diagrams that 
            help you understand complex systems.
          </p>
        </Col>
      </Row>
      
      <Row>
        <Col md={4} className="mb-4">
          <Card>
            <Card.Body>
              <Card.Title>Source Code Analysis</Card.Title>
              <Card.Text>
                Analyze Java and iBatis source code to extract class structures, 
                methods, and dependencies.
              </Card.Text>
              <Button as={Link} to="/projects" variant="primary">
                View Projects
              </Button>
            </Card.Body>
          </Card>
        </Col>
        
        <Col md={4} className="mb-4">
          <Card>
            <Card.Body>
              <Card.Title>Interactive Diagrams</Card.Title>
              <Card.Text>
                Generate sequence diagrams, class diagrams, component diagrams,
                and more from your code.
              </Card.Text>
              <Button as={Link} to="/projects" variant="primary">
                Create Diagrams
              </Button>
            </Card.Body>
          </Card>
        </Col>
        
        <Col md={4} className="mb-4">
          <Card>
            <Card.Body>
              <Card.Title>Export & Share</Card.Title>
              <Card.Text>
                Export diagrams as PDF files for documentation and sharing
                with your team.
              </Card.Text>
              <Button as={Link} to="/projects" variant="primary">
                View Diagrams
              </Button>
            </Card.Body>
          </Card>
        </Col>
      </Row>
    </Container>
  );
};

export default HomePage;

src/pages/ProjectListPage.tsx

import React, { useEffect, useState } from 'react';
import { observer } from 'mobx-react-lite';
import { Link } from 'react-router-dom';
import { Container, Row, Col, Card, Button, Table, Spinner, Modal, Form } from 'react-bootstrap';
import { useForm } from 'react-hook-form';
import { rootStore } from '../store';
import { ProjectCreateRequest } from '../types';

const ProjectListPage: React.FC = observer(() => {
  const { projects, isLoadingProjects, loadProjects, createProject } = rootStore;
  const [showModal, setShowModal] = useState(false);
  const { register, handleSubmit, formState: { errors }, reset } = useForm<ProjectCreateRequest>();
  
  useEffect(() => {
    loadProjects();
  }, []);
  
  const handleCreateProject = async (data: ProjectCreateRequest) => {
    await createProject(data as any);
    setShowModal(false);
    reset();
  };
  
  return (
    <Container className="py-4">
      <Row className="mb-4">
        <Col>
          <h1>Projects</h1>
        </Col>
        <Col xs="auto">
          <Button variant="primary" onClick={() => setShowModal(true)}>
            Create New Project
          </Button>
        </Col>
      </Row>
      
      {isLoadingProjects ? (
        <div className="text-center py-5">
          <Spinner animation="border" role="status">
            <span className="visually-hidden">Loading...</span>
          </Spinner>
        </div>
      ) : projects.length === 0 ? (
        <Card className="text-center py-5">
          <Card.Body>
            <Card.Title>No Projects Found</Card.Title>
            <Card.Text>
              Create a new project to get started with code analysis and visualization.
            </Card.Text>
            <Button variant="primary" onClick={() => setShowModal(true)}>
              Create New Project
            </Button>
          </Card.Body>
        </Card>
      ) : (
        <Table striped bordered hover>
          <thead>
            <tr>
              <th>Name</th>
              <th>Description</th>
              <th>Analysis Date</th>
              <th>Version</th>
              <th>Actions</th>
            </tr>
          </thead>
          <tbody>
            {projects.map(project => (
              <tr key={project.id}>
                <td>{project.name}</td>
                <td>{project.description}</td>
                <td>{new Date(project.analysisDate).toLocaleString()}</td>
                <td>{project.version}</td>
                <td>
                  <Button 
                    variant="outline-primary" 
                    size="sm"
                    as={Link} 
                    to={`/projects/${project.id}`}
                    className="me-2"
                  >
                    Details
                  </Button>
                  <Button 
                    variant="outline-success" 
                    size="sm"
                    as={Link} 
                    to={`/projects/${project.id}/diagrams`}
                  >
                    Diagrams
                  </Button>
                </td>
              </tr>
            ))}
          </tbody>
        </Table>
      )}
      
      {/* Create Project Modal */}
      <Modal show={showModal} onHide={() => setShowModal(false)}>
        <Modal.Header closeButton>
          <Modal.Title>Create New Project</Modal.Title>
        </Modal.Header>
        <Form onSubmit={handleSubmit(handleCreateProject)}>
          <Modal.Body>
            <Form.Group className="mb-3">
              <Form.Label>Project Name</Form.Label>
              <Form.Control 
                type="text" 
                {...register('name', { required: 'Project name is required' })}
                isInvalid={!!errors.name}
              />
              <Form.Control.Feedback type="invalid">
                {errors.name?.message}
              </Form.Control.Feedback>
            </Form.Group>
            
            <Form.Group className="mb-3">
              <Form.Label>Description</Form.Label>
              <Form.Control 
                as="textarea" 
                rows={3} 
                {...register('description')}
              />
            </Form.Group>
            
            <Form.Group className="mb-3">
              <Form.Label>Java Source Path</Form.Label>
              <Form.Control 
                type="text" 
                {...register('javaSourcePath', { required: 'Java source path is required' })}
                isInvalid={!!errors.javaSourcePath}
                placeholder="C:\work_back\viw_ep_edi\src\main\java\com\viw\esp\online"
              />
              <Form.Control.Feedback type="invalid">
                {errors.javaSourcePath?.message}
              </Form.Control.Feedback>
            </Form.Group>
            
            <Form.Group className="mb-3">
              <Form.Label>iBatis Source Path</Form.Label>
              <Form.Control 
                type="text" 
                {...register('ibatisSourcePath', { required: 'iBatis source path is required' })}
                isInvalid={!!errors.ibatisSourcePath}
                placeholder="C:\work_back\viw_erp_wms\src\main\resources\sql"
              />
              <Form.Control.Feedback type="invalid">
                {errors.ibatisSourcePath?.message}
              </Form.Control.Feedback>
            </Form.Group>
            
            <Form.Group className="mb-3">
              <Form.Label>Version</Form.Label>
              <Form.Control 
                type="text" 
                {...register('version')}
              />
            </Form.Group>
          </Modal.Body>
          <Modal.Footer>
            <Button variant="secondary" onClick={() => setShowModal(false)}>
              Cancel
            </Button>
            <Button variant="primary" type="submit">
              Create Project
            </Button>
          </Modal.Footer>
        </Form>
      </Modal>
    </Container>
  );
});

export default ProjectListPage;

src/pages/ProjectDetailPage.tsx

import React, { useEffect, useState } from 'react';
import { observer } from 'mobx-react-lite';
import { useParams, Link } from 'react-router-dom';
import { Container, Row, Col, Card, Button, Tabs, Tab, ListGroup, Badge, Spinner } from 'react-bootstrap';
import { rootStore } from '../store';
import { CodeClass, Method, SqlQuery } from '../types';

interface ParamTypes {
  projectId: string;
}

const ProjectDetailPage: React.FC = observer(() => {
  const { projectId } = useParams<ParamTypes>();
  const { 
    selectedProject, 
    classes, 
    methods, 
    sqlQueries,
    isLoadingProject, 
    isLoadingClasses, 
    isLoadingMethods, 
    isLoadingSqlQueries,
    loadProject,
    loadClassesByProject,
    loadMethodsByClass,
    loadSqlQueriesByProject,
    setSelectedClass
  } = rootStore;
  
  const [selectedClassId, setSelectedClassId] = useState<number | null>(null);
  
  useEffect(() => {
    const id = parseInt(projectId);
    loadProject(id);
    loadClassesByProject(id);
    loadSqlQueriesByProject(id);
  }, [projectId]);
  
  const handleClassClick = (cls: CodeClass) => {
    setSelectedClassId(cls.id);
    setSelectedClass(cls);
    loadMethodsByClass(cls.id);
  };
  
  if (isLoadingProject) {
    return (
      <div className="text-center py-5">
        <Spinner animation="border" role="status">
          <span className="visually-hidden">Loading project...</span>
        </Spinner>
      </div>
    );
  }
  
  if (!selectedProject) {
    return (
      <Container className="py-4">
        <Card className="text-center py-5">
          <Card.Body>
            <Card.Title>Project Not Found</Card.Title>
            <Button as={Link} to="/projects" variant="primary">
              Back to Projects
            </Button>
          </Card.Body>
        </Card>
      </Container>
    );
  }
  
  return (
    <Container fluid className="py-4">
      <Row className="mb-4">
        <Col>
          <h1>{selectedProject.name}</h1>
          <p>{selectedProject.description}</p>
        </Col>
        <Col xs="auto">
          <Button 
            variant="primary" 
            as={Link} 
            to={`/projects/${projectId}/create-diagram`}
            className="me-2"
          >
            Create Diagram
          </Button>
          <Button 
            variant="outline-primary" 
            as={Link} 
            to={`/projects/${projectId}/diagrams`}
          >
            View Diagrams
          </Button>
        </Col>
      </Row>
      
      <Row>
        <Col>
          <Tabs defaultActiveKey="classes" className="mb-3">
            <Tab eventKey="classes" title="Classes">
              <Row>
                <Col md={4}>
                  <Card>
                    <Card.Header>
                      <strong>Classes</strong>
                      {isLoadingClasses && (
                        <Spinner animation="border" size="sm" className="ms-2" />
                      )}
                    </Card.Header>
                    <ListGroup variant="flush" style={{ maxHeight: '600px', overflowY: 'auto' }}>
                      {classes.map(cls => (
                        <ListGroup.Item 
                          key={cls.id} 
                          action 
                          active={selectedClassId === cls.id}
                          onClick={() => handleClassClick(cls)}
                        >
                          <div className="d-flex justify-content-between align-items-center">
                            <div>
                              <div className="fw-bold">{cls.name}</div>
                              <small className="text-muted">{cls.packagePath}</small>
                            </div>
                            <div>
                              <Badge bg={cls.type === 'CLASS' ? 'primary' : 'info'}>
                                {cls.type}
                              </Badge>
                              {cls.isComponent && (
                                <Badge bg="success" className="ms-1">
                                  {cls.componentType}
                                </Badge>
                              )}
                            </div>
                          </div>
                        </ListGroup.Item>
                      ))}
                    </ListGroup>
                  </Card>
                </Col>
                <Col md={8}>
                  <Card>
                    <Card.Header>
                      <strong>Methods</strong>
                      {isLoadingMethods && (
                        <Spinner animation="border" size="sm" className="ms-2" />
                      )}
                    </Card.Header>
                    <ListGroup variant="flush" style={{ maxHeight: '600px', overflowY: 'auto' }}>
                      {selectedClassId === null ? (
                        <ListGroup.Item>Select a class to view its methods</ListGroup.Item>
                      ) : methods.length === 0 ? (
                        <ListGroup.Item>No methods found</ListGroup.Item>
                      ) : (
                        methods.map(method => (
                          <ListGroup.Item key={method.id}>
                            <div className="d-flex justify-content-between align-items-center">
                              <div>
                                <div className="fw-bold">{method.name}</div>
                                <small className="text-muted">{method.signature}</small>
                              </div>
                              <div>
                                <Button 
                                  variant="outline-primary" 
                                  size="sm"
                                  as={Link}
                                  to={`/projects/${projectId}/create-diagram?type=sequence&methodId=${method.id}`}
                                >
                                  Sequence Diagram
                                </Button>
                              </div>
                            </div>
                          </ListGroup.Item>
                        ))
                      )}
                    </ListGroup>
                  </Card>
                </Col>
              </Row>
            </Tab>
            <Tab eventKey="sql" title="SQL Queries">
              <Card>
                <Card.Header>
                  <strong>SQL Queries</strong>
                  {isLoadingSqlQueries && (
                    <Spinner animation="border" size="sm" className="ms-2" />
                  )}
                </Card.Header>
                <ListGroup variant="flush" style={{ maxHeight: '600px', overflowY: 'auto' }}>
                  {sqlQueries.length === 0 ? (
                    <ListGroup.Item>No SQL queries found</ListGroup.Item>
                  ) : (
                    sqlQueries.map(query => (
                      <ListGroup.Item key={query.id}>
                        <div>
                          <div className="d-flex justify-content-between">
                            <div className="fw-bold">
                              {query.namespace}.{query.queryId}
                            </div>
                            <Badge bg={
                              query.queryType === 'SELECT' ? 'success' :
                              query.queryType === 'INSERT' ? 'primary' :
                              query.queryType === 'UPDATE' ? 'warning' :
                              query.queryType === 'DELETE' ? 'danger' : 'secondary'
                            }>
                              {query.queryType}
                            </Badge>
                          </div>
                          <small className="text-muted d-block mt-1">{query.filePath}</small>
                          <pre className="mt-2 p-2 bg-light rounded">
                            <code>{query.queryText.substring(0, 200)}...</code>
                          </pre>
                        </div>
                      </ListGroup.Item>
                    ))
                  )}
                </ListGroup>
              </Card>
            </Tab>
          </Tabs>
        </Col>
      </Row>
    </Container>
  );
});

export default ProjectDetailPage;

src/pages/DiagramListPage.tsx

import React, { useEffect } from 'react';
import { observer } from 'mobx-react-lite';
import { useParams, Link } from 'react-router-dom';
import { Container, Row, Col, Card, Button, Spinner, Table } from 'react-bootstrap';
import { rootStore } from '../store';

interface ParamTypes {
  projectId: string;
}

const DiagramListPage: React.FC = observer(() => {
  const { projectId } = useParams<ParamTypes>();
  const { 
    selectedProject, 
    diagrams, 
    isLoadingProject, 
    isLoadingDiagrams,
    loadProject,
    loadDiagramsByProject
  } = rootStore;
  
  useEffect(() => {
    const id = parseInt(projectId);
    loadProject(id);
    loadDiagramsByProject(id);
  }, [projectId]);
  
  if (isLoadingProject || isLoadingDiagrams) {
    return (
      <div className="text-center py-5">
        <Spinner animation="border" role="status">
          <span className="visually-hidden">Loading...</span>
        </Spinner>
      </div>
    );
  }
  
  if (!selectedProject) {
    return (
      <Container className="py-4">
        <Card className="text-center py-5">
          <Card.Body>
            <Card.Title>Project Not Found</Card.Title>
            <Button as={Link} to="/projects" variant="primary">
              Back to Projects
            </Button>
          </Card.Body>
        </Card>
      </Container>
    );
  }
  
  return (
    <Container className="py-4">
      <Row className="mb-4">
        <Col>
          <h1>Diagrams for {selectedProject.name}</h1>
        </Col>
        <Col xs="auto">
          <Button 
            variant="primary" 
            as={Link} 
            to={`/projects/${projectId}/create-diagram`}
          >
            Create New Diagram
          </Button>
        </Col>
      </Row>
      
      {diagrams.length === 0 ? (
        <Card className="text-center py-5">
          <Card.Body>
            <Card.Title>No Diagrams Found</Card.Title>
            <Card.Text>
              Create a new diagram to visualize the structure and behavior of your code.
            </Card.Text>
            <Button 
              variant="primary" 
              as={Link} 
              to={`/projects/${projectId}/create-diagram`}
            >
              Create New Diagram
            </Button>
          </Card.Body>
        </Card>
      ) : (
        <Table striped bordered hover>
          <thead>
            <tr>
              <th>Name</th>
              <th>Type</th>
              <th>Description</th>
              <th>Last Modified</th>
              <th>Actions</th>
            </tr>
          </thead>
          <tbody>
            {diagrams.map(diagram => (
              <tr key={diagram.id}>
                <td>{diagram.name}</td>
                <td>{diagram.type}</td>
                <td>{diagram.description}</td>
                <td>{new Date(diagram.lastModified).toLocaleString()}</td>
                <td>
                  <Button 
                    variant="outline-primary" 
                    size="sm"
                    as={Link} 
                    to={`/diagrams/${diagram.id}`}
                  >
                    View
                  </Button>
                </td>
              </tr>
            ))}
          </tbody>
        </Table>
      )}
      
      <div className="mt-4">
        <Button variant="secondary" as={Link} to={`/projects/${projectId}`}>
          Back to Project
        </Button>
      </div>
    </Container>
  );
});

export default DiagramListPage;

src/pages/DiagramDetailPage.tsx

import React, { useEffect, useRef } from 'react';
import { observer } from 'mobx-react-lite';
import { useParams, Link } from 'react-router-dom';
import { Container, Row, Col, Card, Button, Spinner } from 'react-bootstrap';
import mermaid from 'mermaid';
import { rootStore } from '../store';
import { exportToPdf } from '../utils/export';

interface ParamTypes {
  diagramId: string;
}

const DiagramDetailPage: React.FC = observer(() => {
  const { diagramId } = useParams<ParamTypes>();
  const { 
    selectedDiagram,
    diagramContent,
    isLoadingDiagrams,
    setSelectedDiagram,
    loadDiagramContent
  } = rootStore;
  
  const diagramRef = useRef<HTMLDivElement>(null);
  
  useEffect(() => {
    const id = parseInt(diagramId);
    
    // Configure mermaid
    mermaid.initialize({
      startOnLoad: true,
      theme: 'default',
      securityLevel: 'loose'
    });
    
    loadDiagramContent(id);
    
    // Clean up
    return () => {
      setSelectedDiagram(null);
    };
  }, [diagramId]);
  
  useEffect(() => {
    if (diagramContent && diagramRef.current) {
      // Clear previous content
      diagramRef.current.innerHTML = '';
      
      // Set content
      diagramRef.current.innerHTML = diagramContent;
      
      // Initialize mermaid diagrams
      mermaid.init(undefined, diagramRef.current);
    }
  }, [diagramContent]);
  
  const handleExportPdf = () => {
    if (diagramRef.current && selectedDiagram) {
      exportToPdf(diagramRef.current, selectedDiagram.name);
    }
  };
  
  if (isLoadingDiagrams) {
    return (
      <div className="text-center py-5">
        <Spinner animation="border" role="status">
          <span className="visually-hidden">Loading diagram...</span>
        </Spinner>
      </div>
    );
  }
  
  return (
    <Container fluid className="py-4">
      <Row className="mb-3">
        <Col>
          <h1>{selectedDiagram?.name}</h1>
          <p>{selectedDiagram?.description}</p>
        </Col>
        <Col xs="auto">
          <Button 
            variant="primary" 
            onClick={handleExportPdf}
          >
            Export to PDF
          </Button>
        </Col>
      </Row>
      
      <Card>
        <Card.Body>
          <div ref={diagramRef} className="diagram-container">
            {/* Diagram content will be rendered here */}
          </div>
        </Card.Body>
      </Card>
    </Container>
  );
});

export default DiagramDetailPage;

src/pages/CreateDiagramPage.tsx

import React, { useEffect, useState } from 'react';
import { observer } from 'mobx-react-lite';
import { useParams, useLocation, useHistory } from 'react-router-dom';
import { Container, Row, Col, Card, Button, Form, Spinner } from 'react-bootstrap';
import { useForm } from 'react-hook-form';
import { rootStore } from '../store';
import { DiagramCreateRequest } from '../types';

interface ParamTypes {
  projectId: string;
}

const CreateDiagram

클래스 다이어그램(Class Diagram)
객체 다이어그램(Object  Diagram)
복합 구조 다이어그램(Complex Structure Diagram)
배포 다이어그램(Deployment Diagram)
컴포넌트 다이어그램(Component Diagram)
패키지 다이어그램(Package Diagram)
프로필 다이어그램
활동 다이어그램(Activity Diagram)
상태 머신 다이어그램(State Machine Diagram)
유스케이스 다이어그램(Use Case Diagram)
상호작용 다이어그램(Interaction Diagram)
커뮤니케이션 다이어그램(Communication Diagram)
시퀀스 다이어그램(Sequence Diagram)
타이밍 다이어그램플로우차트 (Flowchart)
플로우차트 특징플로우차트 기호플로우차트
프로그램 순서도
UML, 플로우차트
마인드맵 (Mind Map)

code-analyzer/
├── backend/ # 백엔드 프로젝트
│ ├── src/main/java/com/codemap/
│ │ ├── CodeAnalyzerApplication.java
│ │ ├── batch/ # 배치 작업 관련 클래스
│ │ ├── controller/ # REST API 컨트롤러
│ │ ├── dto/ # 데이터 전송 객체
│ │ ├── entity/ # JPA 엔티티 클래스
│ │ ├── repository/ # 데이터 액세스 계층
│ │ └── service/ # 비즈니스 로직 서비스
│ ├── src/main/resources/
│ │ ├── application.properties # 애플리케이션 설정
│ │ └── schema.sql # 데이터베이스 스키마 스크립트
│ └── pom.xml # Maven 빌드 설정
└── frontend/ # 프론트엔드 프로젝트

├── public/                   # 정적 파일
├── src/
│   ├── components/           # React 컴포넌트
│   ├── services/             # API 서비스
│   ├── types/                # TypeScript 타입 정의
│   ├── App.tsx               # 메인 앱 컴포넌트
│   └── index.tsx             # 진입점
├── package.json              # NPM 패키지 설정
└── tsconfig.json             # TypeScript 설정