- 외부 DB id 비교를 strict === 에서 toString() 기반 string 비교로 변경 — number/string 어느 쪽으로 오든 매칭. find 실패로 toConnection=null 되면 auto-select useEffect 가 "내부 DB" 로 강제 복귀시키던 문제 해소 - 연결 변경 시 toTables/fromTables 즉시 초기화 — fetch 실패해도 직전 DB 의 테이블이 잔존하지 않도록 - 배치 파이프라인 / 외부커넥션 멀티 DB 작업 핸드오프 노트 함께 추가 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7.9 KiB
외부 DB 커넥션 멀티 DB 지원 + UI 정돈 핸드오프
작업자: hjjeong 날짜: 2026-05-18 관련 PR: #21 (머지 완료, main 반영) 관련 커밋:
d61777abfix(admin): 외부커넥션 mapper varchar 캐스팅 + 외부커넥션/배치관리 UI 정돈46707bd1feat(admin): 외부 DB 커넥션 멀티 DB 테스트 + 프로비저닝 시퀀스 reset 보강
1. 배경
/admin/automaticMng/exconList (외부커넥션관리) 페이지가 작동은 했지만 다음 문제가 누적:
- 페이지 자체 스크롤이 안 생겨 잘림, 컬럼/탭 폰트가 다른 admin 페이지보다 큼
- 연결 테스트 시 500 (mapper SQL 의 VARCHAR vs bigint 비교 오류)
- 비-PostgreSQL DB 등록은 되는데 테스트 단계에서 "PostgreSQL 만 지원" 가드로 막힘
- 모달이 길어지면 저장/취소 버튼이 화면 밖으로 밀려나감
- 회사 프로비저닝 시 외부커넥션 INSERT 가
duplicate key (id)=(5)로 실패
배치관리 (/admin/automaticMng/batchmngList) 도 같은 컨테이너 잘림 + 페이지네이션 부재.
2. 변경 사항
2.1 UI / 레이아웃
frontend/app/(main)/admin/automaticMng/exconList/page.tsx
- 컨테이너:
flex h-full min-h-0 overflow-hidden+ Tabs/TabsContent 가 flex 컬럼 → 페이지는 viewport 에 고정, 테이블만 자체 스크롤 - 폰트 컴팩트:
text-3xl → text-lg,text-sm → text-xs,h-10 → h-8 - ResponsiveDataView 에
scrollContainer+compact활성화
frontend/components/admin/RestApiConnectionList.tsx
- 동일 패턴:
flex flex-1 min-h-0 overflow-hidden컨테이너,<Table divClassName="flex-1 overflow-auto">,<TableHeader className="sticky top-0 z-10 bg-muted"> - 테이블 헤더/행 컴팩트화
frontend/components/common/ResponsiveDataView.tsx
compact모드일 때 폰트/셀패딩/카드 폰트도 함께 축소scrollContainer모드일 때@3xl:block이flex를 덮어쓰던 우선순위 충돌 수정 (@3xl:flex+flex-col분기)- sticky header bg 알파 50% → 100% (bg-muted) — 본문이 헤더 뒤로 비치던 문제
frontend/components/admin/ExternalDbConnectionModal.tsx
- DialogContent 를 flex 컬럼으로, 본문 div 자체 스크롤, DialogFooter
shrink-0→ 폼이 길어도 저장/취소 항상 보임
frontend/app/(main)/admin/automaticMng/batchmngList/page.tsx
- 컨테이너 잘림 해결 (동일 패턴)
- 페이지네이션 추가: RPS
vexplor_rps배치관리 페이지 참고.Pagination컴포넌트(frontend/components/common/Pagination.tsx) 활용. 페이지당 10/20/50/100 선택, 필터 변경 시 1페이지 리셋
2.2 mapper SQL — VARCHAR 캐스팅
V001 legacy 마이그레이션(notes/gbpark/2026-05-03-legacy-sql-archive/V001__varchar_migration.sql)이 EXTERNAL_DB_CONNECTIONS 의 다음 컬럼들을 VARCHAR 로 바꿔놨는데 mapper 가 안 따라가서 500 발생:
| 컬럼 | 원래 타입 | V001 후 | 영향 SQL |
|---|---|---|---|
ID |
bigint | varchar | WHERE ID = #{id} 비교 |
port |
int | varchar | INSERT/UPDATE 바인딩 |
connection_timeout |
int | varchar | 동일 |
query_timeout |
int | varchar | 동일 |
max_connections |
int | varchar | 동일 |
backend-spring/src/main/resources/mapper/externalDbConnection.xml 수정 — 모든 위치에 ::varchar 캐스팅 추가:
- 단건 조회 / 비밀번호 조회 / 이름 중복 체크(exclude) / UPDATE / DELETE 의 ID 비교 6곳
- INSERT/UPDATE 의 숫자→VARCHAR 컬럼 4종
이미 externalRestApiConnection.xml 은 캐스팅 처리돼 있어서(REST API 테스트는 정상이었음) 정작 누락된 건 DB mapper 만이었음. 동일 패턴이 다른 mapper 에도 잠재. 의심되는 mapper 가 있으면 WHERE *_id = #{*_id} 패턴 검색해서 ::varchar 캐스트 보강 필요.
2.3 백엔드 — 멀티 DB 테스트 지원
backend-spring/build.gradle — 드라이버 4종 runtimeOnly 추가:
runtimeOnly 'org.mariadb.jdbc:mariadb-java-client:3.4.1'
runtimeOnly 'com.mysql:mysql-connector-j:8.4.0'
runtimeOnly 'com.microsoft.sqlserver:mssql-jdbc:12.8.1.jre11'
runtimeOnly 'org.xerial:sqlite-jdbc:3.46.1.0'
Oracle 은 라이선스로 미포함 (UI 에 옵션 있지만 백엔드 default 분기로 "지원하지 않는 DB 타입" 응답).
backend-spring/src/main/java/com/erp/service/ExternalDbConnectionService.java
executeConnectionTest의 PostgreSQL-only 가드 제거switch (type)으로 dbType 별 JDBC URL/props 분기:- postgresql:
jdbc:postgresql://h:p/d+connect_timeout/sslmode=require - mysql:
jdbc:mysql://h:p/d+connectTimeout(ms) /useSSL/allowPublicKeyRetrieval - mariadb:
jdbc:mariadb://h:p/d+connectTimeout(ms) /useSsl - mssql/sqlserver:
jdbc:sqlserver://h:p;databaseName=d;loginTimeout=...;encrypt=... - sqlite:
jdbc:sqlite:<database_name 을 파일경로로>(host/port 무시)
- postgresql:
defaultPort(String dbType)헬퍼 추가 (mysql/mariadb=3306, mssql=1433 등)
2.4 프로비저닝 — VARCHAR PK 시퀀스 reset
문제: 회사 신규 생성 시 DataCopier.resetSequences() 가 integer 컬럼만 setval 하고 VARCHAR PK 는 건너뛰어, V001 으로 INT→VARCHAR 변환됐지만 DEFAULT nextval(...) 의존성이 남은 컬럼(external_db_connections.id 등) 이 매번 새 회사에서 충돌.
원래 코드의 의도 (DataCopier.java 의 주석):
레거시 DB 에선 SERIAL 이었다가 나중에 TEXT 로 타입 변경된 컬럼이 있을 수 있음. 이런 컬럼에 setval 을 호출하면 "COALESCE types text and integer cannot be matched" 예외 발생. invyone 은 대다수 PK 가 VARCHAR (문자열 PK). 시퀀스가 연결되어 있어도 실제 INSERT 때 nextval 을 사용하지 않으므로 setval 은 no-op.
→ 두 번째 가정이 V001 영향 테이블에는 안 맞았음.
backend-spring/src/main/java/com/erp/provisioning/DataCopier.java resetSequences() 확장:
isIntegerLike()외에isVarcharLike()분기 추가- VARCHAR 인 경우
setval(seq, GREATEST(COALESCE((SELECT MAX(col::bigint) FROM tbl WHERE col ~ '^[0-9]+$'), 0), 1))사용 ::bigint명시 캐스트로 COALESCE 타입 충돌 회피^[0-9]+$정규식으로 UUID 같은 비숫자 PK 거름 → 0 으로 떨어져 무해
→ 새 회사 생성 시 자동 처리. 기존 회사 DB 들은 1회성 SQL 필요:
SELECT setval(
pg_get_serial_sequence('external_db_connections', 'id'),
COALESCE((SELECT MAX(id::bigint) FROM external_db_connections), 0)
);
3. 미해결 / 후속 작업
-
암호화 키 mismatch 데이터 —
external_db_connections.password컬럼이 다른 인스턴스에서 가져온 경우(예: VEX or 다른 INVYONE) AES 키가 달라Given final block not properly padded로 복호화 실패. 코드 차원 해결 불가. 각 행 편집해서 비밀번호 재입력하거나, 이전 인스턴스의encryption.secret-key값을 알면 SQL 일괄 재암호화 가능. -
SQL 실행 / 테이블 메타 조회 가드 —
ExternalDbConnectionService.executeQuery(L369),getTables(L406 부근),getColumns(L446 부근) 에 동일한 PostgreSQL-only 가드가 남아있음. 멀티 DB 테스트는 풀었지만 이 기능들은 아직 PG 전용. 사용자 요청 시 같은 패턴으로 풀 수 있음. -
Oracle 드라이버 미포함 —
com.oracle.database.jdbc:ojdbc11추가 + service 의switch에oraclecase 추가하면 됨. 라이선스 검토 필요. -
다른 mapper 의 VARCHAR 캐스팅 누락 잠재 — V001 로 INT→VARCHAR 변환된 컬럼이 다른 mapper 에도 있을 수 있음. 신규 500 보이면 mapper 가
WHERE *_id = #{*_id}패턴인지 먼저 의심.
4. 검증
./gradlew build(backend) — BUILD SUCCESSFUL, unit test 통과npm run build(frontend) — 전체 페이지 빌드 통과 (115+ 라우트)- main 머지 후 fast-forward 동기화 완료