반응형
Oracle ERP에서 각 시스템에서 SQL를 만들어서 문제가 발생 할 수 있는지 녹색, 노랑색, 빨강색으로
나누어 메일로 알림을 보내주는 시스템을 만들었는데 React, Typescript, Spring, ibatis, Mariadb
환경에서 구축 할 수 있도록 코드 생성해주세요.
아래는 Oracle ERP 시스템에서 SQL 실행 상태를 모니터링하고 문제 발생 시 경고 이메일을 보내는 시스템을 구현하는 전체 아키텍처와 주요 코드입니다.
시스템 아키텍처
[Frontend: React+TypeScript] ← REST API → [Backend: Spring Boot] ↔ [MyBatis] ↔ [MariaDB]
↓
[Oracle ERP DB] (주기적 모니터링)
↓
[Email Server]
1. 데이터베이스 설계 (MariaDB)
-- 모니터링 대상 시스템 테이블
CREATE TABLE monitored_systems (
system_id INT PRIMARY KEY AUTO_INCREMENT,
system_name VARCHAR(100) NOT NULL,
oracle_connection VARCHAR(200) NOT NULL,
is_active BOOLEAN DEFAULT TRUE
);
-- 모니터링 SQL 쿼리 테이블
CREATE TABLE monitoring_queries (
query_id INT PRIMARY KEY AUTO_INCREMENT,
system_id INT NOT NULL,
query_name VARCHAR(100) NOT NULL,
sql_query TEXT NOT NULL,
warning_threshold INT,
critical_threshold INT,
check_interval_minutes INT DEFAULT 60,
last_checked DATETIME,
FOREIGN KEY (system_id) REFERENCES monitored_systems(system_id)
);
-- 모니터링 결과 기록 테이블
CREATE TABLE monitoring_results (
result_id INT PRIMARY KEY AUTO_INCREMENT,
query_id INT NOT NULL,
execution_time DATETIME NOT NULL,
result_value DECIMAL(20,4) NOT NULL,
status ENUM('GREEN', 'YELLOW', 'RED') NOT NULL,
details TEXT,
FOREIGN KEY (query_id) REFERENCES monitoring_queries(query_id)
);
-- 이메일 알림 설정 테이블
CREATE TABLE notification_settings (
setting_id INT PRIMARY KEY AUTO_INCREMENT,
query_id INT NOT NULL,
email_recipients TEXT NOT NULL,
notify_on_green BOOLEAN DEFAULT FALSE,
notify_on_yellow BOOLEAN DEFAULT TRUE,
notify_on_red BOOLEAN DEFAULT TRUE,
FOREIGN KEY (query_id) REFERENCES monitoring_queries(query_id)
);
2. 백엔드 구현 (Spring Boot)
application.properties
# MariaDB 설정
spring.datasource.url=jdbc:mariadb://localhost:3306/erp_monitoring
spring.datasource.username=your_username
spring.datasource.password=your_password
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
# Oracle ERP 연결 (예시)
oracle.erp.datasource.url=jdbc:oracle:thin:@//erp-server:1521/ORCL
oracle.erp.datasource.username=erp_user
oracle.erp.datasource.password=erp_password
# 이메일 설정
spring.mail.host=smtp.yourmail.com
spring.mail.port=587
spring.mail.username=your_email@yourmail.com
spring.mail.password=your_email_password
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
MonitoringService.java
@Service
@RequiredArgsConstructor
public class MonitoringService {
private final MonitoringQueryMapper queryMapper;
private final MonitoringResultMapper resultMapper;
private final NotificationService notificationService;
@Scheduled(fixedDelayString = "${monitoring.interval:3600000}")
public void monitorAllSystems() {
List<MonitoringQuery> queries = queryMapper.findAllActiveQueries();
queries.forEach(this::executeAndEvaluateQuery);
}
private void executeAndEvaluateQuery(MonitoringQuery query) {
try {
// Oracle ERP에서 쿼리 실행
double resultValue = queryMapper.executeOracleQuery(
query.getSystem().getOracleConnection(),
query.getSqlQuery());
// 상태 평가
Status status = evaluateStatus(resultValue, query);
// 결과 저장
MonitoringResult result = new MonitoringResult();
result.setQueryId(query.getQueryId());
result.setExecutionTime(LocalDateTime.now());
result.setResultValue(resultValue);
result.setStatus(status);
result.setDetails("Query executed successfully");
resultMapper.insertResult(result);
// 알림 전송
notificationService.sendNotificationIfNeeded(query, result);
} catch (Exception e) {
// 오류 처리
MonitoringResult result = new MonitoringResult();
result.setQueryId(query.getQueryId());
result.setExecutionTime(LocalDateTime.now());
result.setResultValue(-1);
result.setStatus(Status.RED);
result.setDetails("Error: " + e.getMessage());
resultMapper.insertResult(result);
notificationService.sendErrorNotification(query, e);
}
}
private Status evaluateStatus(double value, MonitoringQuery query) {
if (query.getCriticalThreshold() != null && value >= query.getCriticalThreshold()) {
return Status.RED;
} else if (query.getWarningThreshold() != null && value >= query.getWarningThreshold()) {
return Status.YELLOW;
} else {
return Status.GREEN;
}
}
}
NotificationService.java
@Service
@RequiredArgsConstructor
public class NotificationService {
private final JavaMailSender mailSender;
private final NotificationSettingMapper settingMapper;
public void sendNotificationIfNeeded(MonitoringQuery query, MonitoringResult result) {
NotificationSetting setting = settingMapper.findByQueryId(query.getQueryId());
if (setting == null) return;
boolean shouldSend = (result.getStatus() == Status.GREEN && setting.isNotifyOnGreen()) ||
(result.getStatus() == Status.YELLOW && setting.isNotifyOnYellow()) ||
(result.getStatus() == Status.RED && setting.isNotifyOnRed());
if (shouldSend) {
sendEmail(setting.getEmailRecipients(),
buildSubject(query, result),
buildContent(query, result));
}
}
public void sendErrorNotification(MonitoringQuery query, Exception e) {
NotificationSetting setting = settingMapper.findByQueryId(query.getQueryId());
if (setting == null) return;
String subject = "[URGENT] Monitoring Error - " + query.getQueryName();
String content = "An error occurred while executing monitoring query:\n\n" +
"Query: " + query.getQueryName() + "\n" +
"System: " + query.getSystem().getSystemName() + "\n\n" +
"Error Details:\n" + e.getMessage();
sendEmail(setting.getEmailRecipients(), subject, content);
}
private void sendEmail(String recipients, String subject, String content) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(recipients.split(";"));
message.setSubject(subject);
message.setText(content);
mailSender.send(message);
}
private String buildSubject(MonitoringQuery query, MonitoringResult result) {
return "[" + result.getStatus() + "] " + query.getQueryName() +
" - " + query.getSystem().getSystemName();
}
private String buildContent(MonitoringQuery query, MonitoringResult result) {
return "Monitoring Result:\n\n" +
"System: " + query.getSystem().getSystemName() + "\n" +
"Query: " + query.getQueryName() + "\n" +
"Executed at: " + result.getExecutionTime() + "\n" +
"Result Value: " + result.getResultValue() + "\n" +
"Status: " + result.getStatus() + "\n\n" +
"Thresholds:\n" +
"Warning: " + query.getWarningThreshold() + "\n" +
"Critical: " + query.getCriticalThreshold() + "\n\n" +
"Details: " + result.getDetails();
}
}
3. 프론트엔드 구현 (React + TypeScript)
types.ts
export type Status = 'GREEN' | 'YELLOW' | 'RED';
export interface MonitoringSystem {
systemId: number;
systemName: string;
oracleConnection: string;
isActive: boolean;
}
export interface MonitoringQuery {
queryId: number;
systemId: number;
system?: MonitoringSystem;
queryName: string;
sqlQuery: string;
warningThreshold?: number;
criticalThreshold?: number;
checkIntervalMinutes: number;
lastChecked?: Date;
}
export interface MonitoringResult {
resultId: number;
queryId: number;
query?: MonitoringQuery;
executionTime: Date;
resultValue: number;
status: Status;
details: string;
}
export interface NotificationSetting {
settingId: number;
queryId: number;
emailRecipients: string;
notifyOnGreen: boolean;
notifyOnYellow: boolean;
notifyOnRed: boolean;
}
Dashboard.tsx
import React, { useEffect, useState } from 'react';
import { MonitoringResult, Status } from './types';
import { fetchRecentResults } from './api';
const Dashboard: React.FC = () => {
const [results, setResults] = useState<MonitoringResult[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadData = async () => {
try {
const data = await fetchRecentResults();
setResults(data);
} catch (error) {
console.error('Failed to fetch results:', error);
} finally {
setLoading(false);
}
};
loadData();
}, []);
const getStatusColor = (status: Status) => {
switch (status) {
case 'GREEN': return 'bg-green-500';
case 'YELLOW': return 'bg-yellow-500';
case 'RED': return 'bg-red-500';
default: return 'bg-gray-500';
}
};
if (loading) return <div>Loading...</div>;
return (
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold mb-6">Oracle ERP Monitoring Dashboard</h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{results.map(result => (
<div key={result.resultId} className="border rounded-lg p-4 shadow">
<div className={`${getStatusColor(result.status)} text-white p-2 rounded-md mb-2`}>
{result.query?.queryName} - {result.status}
</div>
<p>System: {result.query?.system?.systemName}</p>
<p>Value: {result.resultValue}</p>
<p>Executed: {new Date(result.executionTime).toLocaleString()}</p>
<p className="mt-2 text-sm">{result.details}</p>
</div>
))}
</div>
</div>
);
};
export default Dashboard;
QueryForm.tsx
import React, { useState } from 'react';
import { MonitoringQuery, MonitoringSystem } from './types';
import { saveQuery } from './api';
interface QueryFormProps {
systems: MonitoringSystem[];
onSuccess?: () => void;
}
const QueryForm: React.FC<QueryFormProps> = ({ systems, onSuccess }) => {
const [query, setQuery] = useState<Partial<MonitoringQuery>>({
systemId: systems[0]?.systemId,
checkIntervalMinutes: 60
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
await saveQuery(query as MonitoringQuery);
if (onSuccess) onSuccess();
} catch (error) {
console.error('Failed to save query:', error);
}
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block">System</label>
<select
value={query.systemId}
onChange={(e) => setQuery({...query, systemId: parseInt(e.target.value)})}
className="w-full p-2 border rounded"
required
>
{systems.map(system => (
<option key={system.systemId} value={system.systemId}>
{system.systemName}
</option>
))}
</select>
</div>
<div>
<label className="block">Query Name</label>
<input
type="text"
value={query.queryName || ''}
onChange={(e) => setQuery({...query, queryName: e.target.value})}
className="w-full p-2 border rounded"
required
/>
</div>
<div>
<label className="block">SQL Query</label>
<textarea
value={query.sqlQuery || ''}
onChange={(e) => setQuery({...query, sqlQuery: e.target.value})}
className="w-full p-2 border rounded h-32"
required
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block">Warning Threshold</label>
<input
type="number"
value={query.warningThreshold || ''}
onChange={(e) => setQuery({...query, warningThreshold: parseFloat(e.target.value)})}
className="w-full p-2 border rounded"
/>
</div>
<div>
<label className="block">Critical Threshold</label>
<input
type="number"
value={query.criticalThreshold || ''}
onChange={(e) => setQuery({...query, criticalThreshold: parseFloat(e.target.value)})}
className="w-full p-2 border rounded"
/>
</div>
</div>
<div>
<label className="block">Check Interval (minutes)</label>
<input
type="number"
value={query.checkIntervalMinutes || 60}
onChange={(e) => setQuery({...query, checkIntervalMinutes: parseInt(e.target.value)})}
className="w-full p-2 border rounded"
required
/>
</div>
<button type="submit" className="bg-blue-500 text-white px-4 py-2 rounded">
Save Query
</button>
</form>
);
};
export default QueryForm;
4. MyBatis Mapper 예제
MonitoringQueryMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.monitoring.repository.MonitoringQueryMapper">
<select id="findAllActiveQueries" resultType="MonitoringQuery">
SELECT q.*, s.system_name as "system.systemName", s.oracle_connection as "system.oracleConnection"
FROM monitoring_queries q
JOIN monitored_systems s ON q.system_id = s.system_id
WHERE s.is_active = TRUE
</select>
<select id="executeOracleQuery" resultType="double">
<!-- 실제로는 Oracle JDBC를 사용하여 실행 -->
<!-- 이 예제에서는 간단히 표현 -->
${oracleConnection} /* 연결 정보 활용 */
${sqlQuery} /* 실제 쿼리 실행 */
</select>
<insert id="insertResult" parameterType="MonitoringResult" useGeneratedKeys="true" keyProperty="resultId">
INSERT INTO monitoring_results (query_id, execution_time, result_value, status, details)
VALUES (#{queryId}, #{executionTime}, #{resultValue}, #{status}, #{details})
</insert>
</mapper>
5. 시스템 실행 방법
- MariaDB 데이터베이스 설정
- 위의 SQL 스크립트로 데이터베이스 생성
- 테스트 데이터 삽입
- 백엔드 애플리케이션 실행
cd backend
mvn spring-boot:run
3.프론트엔드 애플리케이션 실행
cd frontend
npm install
npm start
6. 추가 고려 사항
- Oracle JDBC 연결 풀 설정
- HikariCP 등을 사용하여 Oracle 연결 풀 구성
- 여러 ERP 시스템 연결을 효율적으로 관리
- 보안 강화
- 데이터베이스 자격 증명 암호화
- API 인증/인가 추가 (JWT 등)
- 고급 기능
- 모니터링 결과 시각화 (차트 등)
- 알림 기록 및 추적 시스템
- 자동 복구 작업 설정
이 시스템은 Oracle ERP에서 실행되는 SQL 쿼리들의 상태를 지속적으로 모니터링하고, 설정된 임계값을 기준으로 녹색(정상), 노란색(경고), 빨간색(위험) 상태를 판단하여 해당하는 담당자에게 이메일 알림을 전송합니다.
반응형