m : resultList) {
- String orgId = m.get("ORG_ID");
- String appId = m.get("APP_ID");
- if (orgId != null && appId != null) {
- result.put(orgId, appId);
- }
- }
- return result;
- }
-}
diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/obp/ObpGwMetricJpaDAO.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/obp/ObpGwMetricJpaDAO.java
new file mode 100644
index 0000000..c734adc
--- /dev/null
+++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/obp/ObpGwMetricJpaDAO.java
@@ -0,0 +1,255 @@
+package com.eactive.eai.rms.data.entity.onl.apim.obp;
+
+import com.eactive.apim.portal.apprequest.entity.AppRequest;
+import com.eactive.apim.portal.apprequest.entity.QAppRequest;
+import com.eactive.eai.data.entity.onl.message.EAIMessageEntity;
+import com.eactive.eai.data.entity.onl.message.QEAIMessageEntity;
+import com.eactive.eai.data.entity.onl.unifbwk.QUnifBwkTp;
+import com.querydsl.core.Tuple;
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Repository;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import java.math.BigDecimal;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * ObpGwMetric JPA DAO - JPA/QueryDSL을 사용한 로그 테이블 집계
+ *
+ * Multi-tenancy 지원: 호출 전 DataSourceContextHolder 설정 필요
+ */
+@Repository
+public class ObpGwMetricJpaDAO {
+
+ private static final Logger log = LoggerFactory.getLogger(ObpGwMetricJpaDAO.class);
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ private JPAQueryFactory getQueryFactory() {
+ return new JPAQueryFactory(entityManager);
+ }
+
+ /**
+ * EAIBZWKDSTCD 코드 목록 조회 (TSEAICM01)
+ *
+ * @return 업무구분코드 목록
+ */
+ public List selectAllBzwkCodes() {
+ QUnifBwkTp cm01 = QUnifBwkTp.unifBwkTp;
+
+ return getQueryFactory()
+ .select(cm01.eaibzwkdstcd)
+ .from(cm01)
+ .orderBy(cm01.eaibzwkdstcd.asc())
+ .fetch();
+ }
+
+ /**
+ * EAIBZWKDSTCD별 시간대 로그 집계 (Native Query)
+ *
+ * 동적 테이블명(TSEAILG1X)을 사용하므로 Native Query 필요
+ *
+ * @param tableName 동적 테이블명 (예: TSEAILG14)
+ * @param bzwkCode 업무구분코드
+ * @param startTime 시작시간 (yyyyMMddHHmmssSSS)
+ * @param endTime 종료시간 (yyyyMMddHHmmssSSS)
+ * @param timeslice 타임슬라이스 (yyyyMMddHH0000000)
+ * @return 집계 결과 목록
+ */
+ @SuppressWarnings("unchecked")
+ public List selectHourlyMetricsByBzwkCode(
+ String tableName, String bzwkCode, String startTime, String endTime, String timeslice) {
+
+ String sql = "SELECT " +
+ "EAISVCNAME AS API_ID, " +
+ "CLIENTID AS CLIENT_ID, " +
+ "EAISEVRINSTNCNAME AS HOSTNAME, " +
+ "ORGID AS ORG_ID, " +
+ ":timeslice AS TIMESLICE, " +
+ "SUM(CASE WHEN LOGPRCSSSERNO = '100' THEN 1 ELSE 0 END) AS ATTEMPTED_COUNT, " +
+ "SUM(CASE WHEN LOGPRCSSSERNO = '400' " +
+ " AND (RSPNSERRCDNAME IS NULL OR RSPNSERRCDNAME LIKE 'ESB%') " +
+ " THEN 1 ELSE 0 END) AS COMPLETED_COUNT, " +
+ "MAX(TRIM(EAISVCSERNO)) AS EAI_SVC_SERNO " +
+ "FROM " + tableName + " " +
+ "WHERE EAIBZWKDSTCD = :bzwkCode " +
+ " AND MSGDPSTYMS BETWEEN :startTime AND :endTime " +
+ "GROUP BY EAISVCNAME, CLIENTID, EAISEVRINSTNCNAME, ORGID " +
+ "HAVING SUM(CASE WHEN LOGPRCSSSERNO = '100' THEN 1 ELSE 0 END) > 0 " +
+ " OR SUM(CASE WHEN LOGPRCSSSERNO = '400' THEN 1 ELSE 0 END) > 0";
+
+ Query query = entityManager.createNativeQuery(sql);
+ query.setParameter("timeslice", timeslice);
+ query.setParameter("bzwkCode", bzwkCode);
+ query.setParameter("startTime", startTime);
+ query.setParameter("endTime", endTime);
+
+ List