From 2a14d6b2129f4ce0fd47fa9fad096c5f7cc5ead0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EC=98=81?=
Date: Wed, 5 Nov 2025 12:41:35 +0900
Subject: [PATCH] =?UTF-8?q?=EC=B0=A8=EB=9F=89=EA=B8=B0=EB=B3=B8=EC=A0=95?=
=?UTF-8?q?=EB=B3=B4=20API,=20DB=20=EC=A0=80=EC=9E=A5=20=EB=A1=9C=EC=A7=81?=
=?UTF-8?q?=20=EC=B6=94=EA=B0=80=20=EC=B6=94=EA=B0=80=EC=A0=81=EC=9C=BC?=
=?UTF-8?q?=EB=A1=9C=20=EC=86=8C=EC=8A=A4=20=EB=A6=AC=ED=8C=A9=ED=86=A0?=
=?UTF-8?q?=EB=A7=81=20=EC=98=88=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
build.gradle | 5 +
ddl/ibmsdb/seq_car_bass_matter_inqire.sql | 11 +
ddl/ibmsdb/tb_car_bass_matter_inqire.sql | 106 ++++
.../client/GovernmentApiClient.java | 355 +++++++-----
.../interfaceapp/config/DatabaseConfig.java | 57 ++
.../com/vmis/interfaceapp/config/Globals.java | 39 ++
.../mapper/CarBassMatterInqireMapper.java | 54 ++
.../interfaceapp/mapper/SampleMapper.java | 26 +
.../model/basic/CarBassMatterInqireVO.java | 524 ++++++++++++++++++
.../service/CarBassMatterInqireService.java | 126 +++++
src/main/resources/application-dev.yml | 38 ++
src/main/resources/application-prd.yml | 38 ++
src/main/resources/application.yml | 4 +
.../CarBassMatterInqireMapper_maria.xml | 148 +++++
.../resources/mybatis/mapper/sample_maria.xml | 44 ++
src/main/resources/mybatis/mybatis-config.xml | 47 ++
16 files changed, 1486 insertions(+), 136 deletions(-)
create mode 100644 ddl/ibmsdb/seq_car_bass_matter_inqire.sql
create mode 100644 ddl/ibmsdb/tb_car_bass_matter_inqire.sql
create mode 100644 src/main/java/com/vmis/interfaceapp/config/DatabaseConfig.java
create mode 100644 src/main/java/com/vmis/interfaceapp/config/Globals.java
create mode 100644 src/main/java/com/vmis/interfaceapp/mapper/CarBassMatterInqireMapper.java
create mode 100644 src/main/java/com/vmis/interfaceapp/mapper/SampleMapper.java
create mode 100644 src/main/java/com/vmis/interfaceapp/model/basic/CarBassMatterInqireVO.java
create mode 100644 src/main/java/com/vmis/interfaceapp/service/CarBassMatterInqireService.java
create mode 100644 src/main/resources/mybatis/mapper/CarBassMatterInqireMapper_maria.xml
create mode 100644 src/main/resources/mybatis/mapper/sample_maria.xml
create mode 100644 src/main/resources/mybatis/mybatis-config.xml
diff --git a/build.gradle b/build.gradle
index 82198f5..21ab4f7 100644
--- a/build.gradle
+++ b/build.gradle
@@ -29,6 +29,11 @@ dependencies {
// GPKI JNI local library
implementation files('lib/libgpkiapi_jni_1.5.jar')
+ // Database & MyBatis
+ implementation 'org.springframework.boot:spring-boot-starter-jdbc'
+ implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
+ implementation 'org.mariadb.jdbc:mariadb-java-client:3.3.3'
+
// Configuration metadata
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
diff --git a/ddl/ibmsdb/seq_car_bass_matter_inqire.sql b/ddl/ibmsdb/seq_car_bass_matter_inqire.sql
new file mode 100644
index 0000000..e9a1f80
--- /dev/null
+++ b/ddl/ibmsdb/seq_car_bass_matter_inqire.sql
@@ -0,0 +1,11 @@
+-- 자동차 기본 사항 조회 ID 시퀀스
+-- CBMI000000000001, CBMI000000000002, ... 형태로 생성
+CREATE SEQUENCE seq_car_bass_matter_inqire
+ START WITH 1
+ INCREMENT BY 1
+ MINVALUE 1
+ MAXVALUE 9999999999
+ CACHE 1000
+ NOCYCLE;
+
+-- SELECT CONCAT('CBMI', LPAD(NEXTVAL(seq_car_bass_matter_inqire), 16, '0'));
\ No newline at end of file
diff --git a/ddl/ibmsdb/tb_car_bass_matter_inqire.sql b/ddl/ibmsdb/tb_car_bass_matter_inqire.sql
new file mode 100644
index 0000000..d86fb32
--- /dev/null
+++ b/ddl/ibmsdb/tb_car_bass_matter_inqire.sql
@@ -0,0 +1,106 @@
+create table tb_car_bass_matter_inqire
+(
+ CAR_BASS_MATTER_INQIRE varchar(20) not null comment '자동차 기본 사항 조회 ID'
+ primary key,
+ INFO_SYS_ID varchar(6) null comment '정보 시스템 ID',
+ INFO_SYS_IP varchar(23) null comment '정보 시스템 IP',
+ SIGUNGU_CODE varchar(5) null comment '시군구 코드',
+ CNTC_INFO_CODE varchar(10) null comment '연계 정보 코드',
+ CHARGER_ID varchar(15) null comment '담당자 ID',
+ CHARGER_IP varchar(23) null comment '담당자 IP',
+ CHARGER_NM varchar(75) null comment '담당자명',
+ DMND_LEVY_STDDE varchar(8) null comment '요청 부과 기준일',
+ DMND_INQIRE_SE_CODE varchar(1) null comment '요청 조회 구분 코드',
+ DMND_VHRNO varchar(30) null comment '요청 자동차등록번호',
+ DMND_VIN varchar(17) null comment '요청 차대번호',
+ CNTC_RESULT_CODE varchar(8) null comment '연계 결과 코드',
+ CNTC_RESULT_DTLS varchar(200) null comment '연계 결과 상세',
+ PRYE varchar(4) null comment '연식',
+ REGIST_DE varchar(8) null comment '등록일',
+ ERSR_REGIST_SE_CODE varchar(4) null comment '말소 등록 구분 코드',
+ ERSR_REGIST_SE_NM varchar(30) null comment '말소 등록 구분명',
+ ERSR_REGIST_DE varchar(8) null comment '말소 등록일',
+ REGIST_DETAIL_CODE varchar(3) null comment '등록 상세 코드',
+ DSPLVL varchar(6) null comment '배기량',
+ USE_STRNGHLD_LEGALDONG_CODE varchar(10) null comment '사용 본거지 법정동 코드',
+ USE_STRNGHLD_ADSTRD_CODE varchar(10) null comment '사용 본거지 행정동 코드',
+ USE_STRNGHLD_MNTN varchar(2) null comment '사용 본거지 산',
+ USE_STRNGHLD_LNBR varchar(4) null comment '사용 본거지 번지',
+ USE_STRNGHLD_HO varchar(4) null comment '사용 본거지 호',
+ USE_STRNGHLD_ADRES_NM varchar(300) null comment '사용 본거지 상세주소',
+ USE_STRNGHLD_ROAD_NM_CODE varchar(12) null comment '사용 본거지 도로명 코드',
+ USGSRHLD_UNDGRND_BULD_SE_CODE varchar(1) null comment '사용 본거지 지하 건물 구분 코드',
+ USE_STRNGHLD_BULD_MAIN_NO varchar(5) null comment '사용 본거지 건물 주요 번호',
+ USE_STRNGHLD_BULD_SUB_NO varchar(5) null comment '사용 본거지 건물 부 번호',
+ USGSRHLD_ADRES_FULL varchar(750) null comment '사용 본거지 전체주소',
+ MBER_SE_CODE varchar(2) null comment '대표소유자 회원 구분 코드',
+ MBER_SE_NO varchar(100) null comment '대표소유자 회원 번호',
+ TELNO varchar(20) null comment '대표소유자 전화번호',
+ OWNER_LEGALDONG_CODE varchar(10) null comment '소유자 법정동 코드',
+ OWNER_ADSTRD_CODE varchar(10) null comment '소유자 행정동 코드',
+ OWNER_MNTN varchar(2) null comment '소유자 산',
+ OWNER_LNBR varchar(4) null comment '소유자 번지',
+ OWNER_HO varchar(4) null comment '소유자 호',
+ OWNER_ADRES_NM varchar(300) null comment '소유자 상세주소',
+ OWNER_ROAD_NM_CODE varchar(12) null comment '소유자 도로명 코드',
+ OWNER_UNDGRND_BULD_SE_CODE varchar(1) null comment '소유자 지하건물 구분 코드',
+ OWNER_BULD_MAIN_NO varchar(5) null comment '소유자 건물 주요 번호',
+ OWNER_BULD_SUB_NO varchar(5) null comment '소유자 건물 부 번호',
+ OWNR_WHOLADDR varchar(750) null comment '소유자 전체주소',
+ AFTR_VHRNO varchar(30) null comment '신 차량번호',
+ USE_FUEL_CODE varchar(1) null comment '사용 연료 코드',
+ PRPOS_SE_CODE varchar(2) null comment '용도 구분 코드',
+ MTRS_FOM_NM varchar(75) null comment '원동기 형식명',
+ FRNT_VHRNO varchar(30) null comment '이전 차량번호',
+ VHCLNO varchar(30) null comment '차량번호',
+ VIN varchar(17) null comment '차대번호',
+ CNM varchar(75) null comment '차명',
+ VHCLE_TOT_WT varchar(6) null comment '차량 총 중량',
+ CAAG_ENDDE varchar(8) null comment '차령 만료일자',
+ CHANGE_DE varchar(8) null comment '차번호 변경시기',
+ VHCTY_ASORT_CODE varchar(1) null comment '차종 종별 코드',
+ VHCTY_TY_CODE varchar(1) null comment '차종 유형 코드',
+ VHCTY_SE_CODE varchar(1) null comment '차종 분류 코드',
+ MXMM_LDG varchar(10) null comment '최대 적재량',
+ VHCTY_ASORT_NM varchar(150) null comment '차종 종별명',
+ VHCTY_TY_NM varchar(150) null comment '차종 유형명',
+ VHCTY_SE_NM varchar(150) null comment '차종 분류명',
+ FRST_REGIST_DE varchar(8) null comment '최초 등록일',
+ FOM_NM varchar(75) null comment '형식',
+ ACQS_DE varchar(8) null comment '취득 일자',
+ ACQS_END_DE varchar(8) null comment '취득 종료일자',
+ YBL_MD varchar(8) null comment '제작 년월일',
+ TRANSR_REGIST_DE varchar(8) null comment '이전 등록일',
+ SPCF_REGIST_STTUS_CODE varchar(6) null comment '제원 등록 상태 코드',
+ COLOR_NM varchar(75) null comment '색상명',
+ MRTG_CO varchar(9) null comment '저당수',
+ SEIZR_CO varchar(9) null comment '압류건수',
+ STMD_CO varchar(9) null comment '구조변경수',
+ NMPL_CSDY_AT varchar(1) null comment '번호판 영치 여부',
+ NMPL_CSDY_REMNR_DE varchar(8) null comment '번호판 영치 최고일',
+ ORIGIN_SE_CODE varchar(1) null comment '출처 구분 코드',
+ NMPL_STNDRD_CODE varchar(1) null comment '번호판 규격 코드',
+ ACQS_AMOUNT varchar(18) null comment '취득 금액',
+ INSPT_VALID_PD_BGNDE varchar(8) null comment '검사 유효 기간 시작일',
+ INSPT_VALID_PD_ENDDE varchar(8) null comment '검사 유효 기간 종료일',
+ USE_STRNGHLD_GRC_CODE varchar(4) null comment '사용 본거지 관청 코드',
+ TKCAR_PSCAP_CO varchar(3) null comment '승차정원수',
+ SPMNNO varchar(17) null comment '제원관리번호',
+ TRVL_DSTNC varchar(10) null comment '주행거리',
+ FRST_REGIST_RQRCNO varchar(20) null comment '최초 등록 접수번호',
+ VLNT_ERSR_PRVNTC_NTICE_DE varchar(8) null comment '예고통지일',
+ REGIST_INSTT_NM varchar(150) null comment '등록 기관명',
+ PROCESS_IMPRTY_RESN_CODE varchar(2) null comment '처리 불가 사유 코드',
+ PROCESS_IMPRTY_RESN_DTLS varchar(75) null comment '처리 불가 사유 명세',
+ CBD_LT varchar(10) null comment '차체 길이',
+ CBD_BT varchar(10) null comment '차체 너비',
+ CBD_HG varchar(10) null comment '차체 높이',
+ FRST_MXMM_LDG varchar(10) null comment '최초 최대 적재량',
+ FUEL_CNSMP_RT varchar(5) null comment '연료 소비율',
+ ELCTY_CMPND_FUEL_CNSMP_RT varchar(5) null comment '전기 복합 연료 소비율',
+ REG_DT datetime null comment '등록 일시',
+ RGTR varchar(11) null comment '등록자',
+ MBER_NM varchar(75) null comment '대표소유자 성명'
+)
+ comment '자동차 기본 사항 조회';
+
diff --git a/src/main/java/com/vmis/interfaceapp/client/GovernmentApiClient.java b/src/main/java/com/vmis/interfaceapp/client/GovernmentApiClient.java
index 5e77dd7..eed35c3 100644
--- a/src/main/java/com/vmis/interfaceapp/client/GovernmentApiClient.java
+++ b/src/main/java/com/vmis/interfaceapp/client/GovernmentApiClient.java
@@ -6,10 +6,12 @@ import com.vmis.interfaceapp.config.properties.VmisProperties;
import com.vmis.interfaceapp.gpki.GpkiService;
import com.vmis.interfaceapp.model.basic.BasicRequest;
import com.vmis.interfaceapp.model.basic.BasicResponse;
+import com.vmis.interfaceapp.model.basic.CarBassMatterInqireVO;
import com.vmis.interfaceapp.model.common.Envelope;
import com.vmis.interfaceapp.model.ledger.LedgerRequest;
import com.vmis.interfaceapp.model.ledger.LedgerResponse;
import com.vmis.interfaceapp.util.TxIdUtil;
+import lombok.RequiredArgsConstructor;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpStatusCodeException;
@@ -56,6 +58,7 @@ import java.nio.charset.StandardCharsets;
* @see VmisProperties
*/
@Slf4j
+@RequiredArgsConstructor
@Component
public class GovernmentApiClient {
@@ -122,6 +125,13 @@ public class GovernmentApiClient {
*/
private final ObjectMapper objectMapper;
+ /**
+ * 자동차 기본 사항 조회 로그 서비스
+ *
+ * API 호출 정보를 DB에 로그성으로 저장합니다.
+ */
+ private final com.vmis.interfaceapp.service.CarBassMatterInqireService carBassMatterInqireService;
+
/**
* 서비스 타입 열거형
*
@@ -156,134 +166,6 @@ public class GovernmentApiClient {
*/
LEDGER }
- /**
- * 생성자를 통한 의존성 주입
- *
- * Spring의 생성자 주입 방식을 사용하여 필요한 모든 의존성을 주입받습니다.
- *
- * @param restTemplate HTTP 통신을 위한 RestTemplate 객체
- * @param props VMIS 애플리케이션 설정 속성
- * @param gpkiService GPKI 암호화/복호화 서비스
- * @param objectMapper JSON 직렬화/역직렬화를 위한 ObjectMapper
- * @Component 어노테이션과 함께 사용되어 자동으로 Spring Bean으로 등록됩니다.
생성자 주입의 장점: 필수 의존성을 명확하게 표현 (컴파일 타임에 검증) 불변성 보장 (모든 필드가 final로 선언 가능) 단위 테스트 작성 용이 (Mock 객체 주입 간편) 순환 참조 방지 (애플리케이션 시작 시 감지)
- */
- public GovernmentApiClient(RestTemplate restTemplate, VmisProperties props, GpkiService gpkiService, ObjectMapper objectMapper) {
- this.restTemplate = restTemplate;
- this.props = props;
- this.gpkiService = gpkiService;
- this.objectMapper = objectMapper;
- }
-
- /**
- * 정부 API 호출 (문자열 기반)
- *
- * 이 메서드는 JSON 문자열을 직접 받아 정부 API를 호출하는 저수준(Low-level) 메서드입니다.
- * 타입 안전성이 필요한 경우 {@link #callModel} 메서드를 사용하는 것이 권장됩니다.
- *
- * 처리 흐름:
- *
- * 설정 로드: 서비스 타입에 따라 적절한 정부 API 설정을 가져옴
- * URL 구성: 호스트, 포트, 경로를 결합하여 완전한 URL 생성
- * 트랜잭션 ID 생성: 요청 추적을 위한 고유 ID 생성 (TxIdUtil 사용)
- * 헤더 구성: 필수 헤더(API 키, 시스템 정보, GPKI 설정 등) 추가
- * 암호화 처리: GPKI가 활성화된 경우 요청 바디를 암호화
- * HTTP 요청: RestTemplate을 사용하여 POST 요청 전송
- * 복호화 처리: 성공 응답이고 GPKI가 활성화된 경우 응답 바디를 복호화
- * 응답 반환: 최종 응답을 ResponseEntity로 반환
- *
- *
- * 에러 처리 전략:
- *
- * GPKI 암호화 실패: RuntimeException으로 래핑하여 즉시 중단
- * HTTP 에러 (4xx, 5xx): 에러 응답을 그대로 반환하여 호출자가 처리
- * GPKI 복호화 실패: RuntimeException으로 래핑하여 즉시 중단
- *
- *
- * 로깅:
- *
- * 요청 로그: URL, 트랜잭션 ID, GPKI 활성화 여부, 바디 길이
- * 에러 로그: HTTP 상태 코드, 에러 응답 바디
- *
- *
- * @param type 서비스 타입 (BASIC 또는 LEDGER)
- * @param jsonBody 전송할 JSON 문자열 (암호화 전 평문)
- * @return ResponseEntity<String> 정부 API 응답 (복호화 완료된 JSON 문자열)
- * @throws RuntimeException GPKI 암호화/복호화 실패 시
- */
- public ResponseEntity call(ServiceType type, String jsonBody) {
- // 1. 서비스 타입에 따른 설정 로드
- // props.getGov()는 정부 API 관련 모든 설정을 포함하는 객체를 반환
- VmisProperties.GovProps gov = props.getGov();
-
- // 삼항 연산자를 사용하여 BASIC 또는 LEDGER 서비스 설정 선택
- VmisProperties.GovProps.Service svc = (type == ServiceType.BASIC)
- ? gov.getServices().getBasic()
- : gov.getServices().getLedger();
-
- // 2. 완전한 URL 구성 (예: https://api.gov.kr:8080/vmis/basic)
- String url = gov.buildServiceUrl(svc.getPath());
-
- // 3. 트랜잭션 ID 생성 (요청 추적 및 디버깅 용도)
- // 일반적으로 타임스탬프 + UUID 조합으로 생성됨
- String txId = TxIdUtil.generate();
-
- // 4. HTTP 헤더 구성
- // API 키, 시스템 정보, GPKI 설정 등 필수 헤더 포함
- HttpHeaders headers = buildHeaders(svc, txId);
-
- // 5. GPKI 암호화 처리
- String bodyToSend = jsonBody;
- if (gpkiService.isEnabled()) {
- try {
- // 평문 JSON을 암호화된 문자열로 변환
- // 암호화 결과는 Base64 인코딩된 문자열
- bodyToSend = gpkiService.encrypt(jsonBody);
- } catch (Exception e) {
- // 암호화 실패는 치명적 오류이므로 즉시 예외 발생
- throw new RuntimeException("GPKI 암호화 실패", e);
- }
- }
-
- // 6. HTTP 엔티티 생성 (헤더 + 바디)
- HttpEntity request = new HttpEntity<>(bodyToSend, headers);
-
- // 7. 요청 로그 기록
- // 디버깅을 위해 URL, 트랜잭션 ID, GPKI 활성화 여부, 바디 길이 로깅
- log.info("[GOV-REQ] url={}, tx_id={}, gpki={}, length={}", url, txId, gpkiService.isEnabled(), bodyToSend != null ? bodyToSend.length() : 0);
-
- try {
- // 8. 실제 HTTP POST 요청 전송
- // RestTemplate.exchange()는 HTTP 메서드, 헤더, 바디를 모두 지정 가능
- ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, request, String.class);
- String respBody = response.getBody();
-
- // 9. 성공 응답인 경우 GPKI 복호화 처리
- if (gpkiService.isEnabled() && response.getStatusCode().is2xxSuccessful()) {
- try {
- // 암호화된 응답을 평문 JSON으로 복호화
- String decrypted = gpkiService.decrypt(respBody);
- // 복호화된 바디로 새로운 ResponseEntity 생성
- // 원본 응답의 상태 코드와 헤더는 그대로 유지
- return ResponseEntity.status(response.getStatusCode()).headers(response.getHeaders()).body(decrypted);
- } catch (Exception e) {
- // 복호화 실패는 치명적 오류이므로 즉시 예외 발생
- throw new RuntimeException("GPKI 복호화 실패", e);
- }
- }
- // GPKI가 비활성화되어 있거나 에러 응답인 경우 원본 그대로 반환
- return response;
- } catch (HttpStatusCodeException ex) {
- // 10. HTTP 에러 처리 (4xx, 5xx 상태 코드)
- // 경고 로그 기록 (에러는 정상적인 비즈니스 흐름일 수 있으므로 WARN 레벨)
- log.warn("[GOV-ERR] status={}, body={}", ex.getStatusCode(), ex.getResponseBodyAsString());
-
- // 에러 응답을 ResponseEntity로 변환하여 반환
- // 호출자가 상태 코드와 에러 메시지를 확인할 수 있도록 함
- // 헤더가 null인 경우를 대비하여 빈 HttpHeaders 사용
- return ResponseEntity.status(ex.getStatusCode()).headers(ex.getResponseHeaders() != null ? ex.getResponseHeaders() : new HttpHeaders()).body(ex.getResponseBodyAsString());
- }
- }
-
/**
* HTTP 헤더 구성
*
@@ -398,10 +280,6 @@ public class GovernmentApiClient {
// CVMIS(Car Vehicle Management Information System) 전용 API 키
headers.add("cvmis_apikey", svc.getCvmisApikey());
- // 9. 정보시스템 ID
- // 호출하는 시스템을 식별하는 필수 값
- headers.add("INFO_SYS_ID", props.getSystem().getInfoSysId());
-
// 구성 완료된 헤더 반환
return headers;
}
@@ -414,11 +292,20 @@ public class GovernmentApiClient {
*
* 특징:
*
- * 제네릭 타입으로 컴파일 타임 타입 체크
+ * 제네릭 타입으로 컴파일 타입 타입 체크
* 요청/응답 객체가 Envelope로 감싸져 있음
* Jackson TypeReference를 사용한 제네릭 역직렬화
+ * API 호출 전후로 DB에 로그성 데이터 저장
*
*
+ * 처리 흐름:
+ *
+ * 요청 정보를 DB에 INSERT (로그 저장)
+ * 정부 API 호출
+ * 응답 정보를 DB에 UPDATE
+ * 에러 발생 시 에러 정보도 DB에 UPDATE
+ *
+ *
* 사용 예시:
*
* BasicRequest request = new BasicRequest();
@@ -435,9 +322,34 @@ public class GovernmentApiClient {
* @return ResponseEntity<Envelope<BasicResponse>> 조회 결과를 담은 응답
*/
public ResponseEntity> callBasic(Envelope envelope) {
- // TypeReference를 사용하여 제네릭 타입 정보 전달
- // 익명 클래스를 생성하여 타입 소거(Type Erasure) 문제 해결
- return callModel(ServiceType.BASIC, envelope, new TypeReference>(){});
+ String generatedId = null;
+
+ try {
+ // 1. 요청 정보 DB 저장 (첫 번째 요청만 저장)
+ if (envelope.getData() != null && !envelope.getData().isEmpty()) {
+ com.vmis.interfaceapp.model.basic.BasicRequest request = envelope.getData().get(0);
+ generatedId = saveRequestLog(request);
+ log.info("[BASIC-REQ-LOG] 요청 정보 저장 완료 - ID: {}, 차량번호: {}", generatedId, request.getVhrno());
+ }
+
+ // 2. 정부 API 호출
+ ResponseEntity> response = callModel(ServiceType.BASIC, envelope, new TypeReference>(){});
+
+ // 3. 응답 정보 DB 업데이트
+ if (generatedId != null && response.getBody() != null) {
+ updateResponseLog(generatedId, response.getBody());
+ log.info("[BASIC-RES-LOG] 응답 정보 저장 완료 - ID: {}", generatedId);
+ }
+
+ return response;
+
+ } catch (Exception e) {
+ // 4. 에러 발생 시 에러 정보 DB 업데이트
+ if (generatedId != null) {
+ updateErrorLog(generatedId, e);
+ }
+ throw e;
+ }
}
/**
@@ -744,4 +656,175 @@ public class GovernmentApiClient {
throw new RuntimeException("GPKI 복호화 실패", e);
}
}
+
+ /**
+ * 요청 정보를 DB에 저장
+ *
+ * @param request 요청 정보
+ * @return 생성된 ID
+ */
+ private String saveRequestLog(com.vmis.interfaceapp.model.basic.BasicRequest request) {
+ CarBassMatterInqireVO logEntity = CarBassMatterInqireVO.builder()
+ .infoSysId(request.getInfoSysId())
+ .infoSysIp(request.getInfoSysIp())
+ .sigunguCode(request.getSigunguCode())
+ .cntcInfoCode(request.getCntcInfoCode())
+ .chargerId(request.getChargerId())
+ .chargerIp(request.getChargerIp())
+ .chargerNm(request.getChargerNm())
+ .dmndLevyStdde(request.getLevyStdde())
+ .dmndInqireSeCode(request.getInqireSeCode())
+ .dmndVhrno(request.getVhrno())
+ .dmndVin(request.getVin())
+ .rgtr("SYSTEM")
+ .build();
+
+ return carBassMatterInqireService.createInitialRequest(logEntity);
+ }
+
+ /**
+ * 응답 정보를 DB에 업데이트
+ *
+ * @param id 저장된 ID
+ * @param envelope 응답 정보
+ */
+ private void updateResponseLog(String id, Envelope envelope) {
+ com.vmis.interfaceapp.model.basic.BasicResponse response = envelope.getData() != null && !envelope.getData().isEmpty()
+ ? envelope.getData().get(0)
+ : null;
+
+ if (response == null) {
+ log.warn("[BASIC-RES-LOG] 응답 데이터가 없습니다 - ID: {}", id);
+ return;
+ }
+
+ // Builder 패턴으로 응답 정보 매핑
+ CarBassMatterInqireVO.CarBassMatterInqireVOBuilder builder = CarBassMatterInqireVO.builder()
+ .carBassMatterInqire(id)
+ .cntcResultCode(response.getCntcResultCode())
+ .cntcResultDtls(response.getCntcResultDtls());
+
+ // record가 있으면 첫 번째 record의 정보를 매핑
+ if (response.getRecord() != null && !response.getRecord().isEmpty()) {
+ com.vmis.interfaceapp.model.basic.BasicResponse.Record record = response.getRecord().get(0);
+ mapRecordToEntity(builder, record);
+ }
+
+ carBassMatterInqireService.updateResponse(builder.build());
+ }
+
+ /**
+ * Record 정보를 Entity에 매핑
+ *
+ * @param builder Entity Builder
+ * @param record Record 정보
+ */
+ private void mapRecordToEntity(CarBassMatterInqireVO.CarBassMatterInqireVOBuilder builder, com.vmis.interfaceapp.model.basic.BasicResponse.Record record) {
+ builder
+ .prye(record.getPrye())
+ .registDe(record.getRegistDe())
+ .ersrRegistSeCode(record.getErsrRegistSeCode())
+ .ersrRegistSeNm(record.getErsrRegistSeNm())
+ .ersrRegistDe(record.getErsrRegistDe())
+ .registDetailCode(record.getRegistDetailCode())
+ .dsplvl(record.getDsplvl())
+ .useStrnghldLegaldongCode(record.getUseStrnghldLegaldongCode())
+ .useStrnghldAdstrdCode(record.getUseStrnghldAdstrdCode())
+ .useStrnghldMntn(record.getUseStrnghldMntn())
+ .useStrnghldLnbr(record.getUseStrnghldLnbr())
+ .useStrnghldHo(record.getUseStrnghldHo())
+ .useStrnghldAdresNm(record.getUseStrnghldAdresNm())
+ .useStrnghldRoadNmCode(record.getUseStrnghldRoadNmCode())
+ .usgsrhldUndgrndBuldSeCode(record.getUsgsrhldUndgrndBuldSeCode())
+ .useStrnghldBuldMainNo(record.getUseStrnghldBuldMainNo())
+ .useStrnghldBuldSubNo(record.getUseStrnghldBuldSubNo())
+ .usgsrhldAdresFull(record.getUsgsrhldAdresFull())
+ .mberSeCode(record.getMberSeCode())
+ .mberSeNo(record.getMberSeNo())
+ .mberNm(record.getMberNm())
+ .telno(record.getTelno())
+ .ownerLegaldongCode(record.getOwnerLegaldongCode())
+ .ownerAdstrdCode(record.getOwnerAdstrdCode())
+ .ownerMntn(record.getOwnerMntn())
+ .ownerLnbr(record.getOwnerLnbr())
+ .ownerHo(record.getOwnerHo())
+ .ownerAdresNm(record.getOwnerAdresNm())
+ .ownerRoadNmCode(record.getOwnerRoadNmCode())
+ .ownerUndgrndBuldSeCode(record.getOwnerUndgrndBuldSeCode())
+ .ownerBuldMainNo(record.getOwnerBuldMainNo())
+ .ownerBuldSubNo(record.getOwnerBuldSubNo())
+ .ownrWholaddr(record.getOwnerAdresFull())
+ .aftrVhrno(record.getAftrVhrno())
+ .useFuelCode(record.getUseFuelCode())
+ .prposSeCode(record.getPrposSeCode())
+ .mtrsFomNm(record.getMtrsFomNm())
+ .frntVhrno(record.getFrntVhrno())
+ .vhclno(record.getVhrno())
+ .vin(record.getVin())
+ .cnm(record.getCnm())
+ .vhcleTotWt(record.getVhcleTotWt())
+ .caagEndde(record.getCaagEndde())
+ .changeDe(record.getChangeDe())
+ .vhctyAsortCode(record.getVhctyAsortCode())
+ .vhctyTyCode(record.getVhctyTyCode())
+ .vhctySeCode(record.getVhctySeCode())
+ .mxmmLdg(record.getMxmmLdg())
+ .vhctyAsortNm(record.getVhctyAsortNm())
+ .vhctyTyNm(record.getVhctyTyNm())
+ .vhctySeNm(record.getVhctySeNm())
+ .frstRegistDe(record.getFrstRegistDe())
+ .fomNm(record.getFomNm())
+ .acqsDe(record.getAcqsDe())
+ .acqsEndDe(record.getAcqsEndDe())
+ .yblMd(record.getYblMd())
+ .transrRegistDe(record.getTransrRegistDe())
+ .spcfRegistSttusCode(record.getSpcfRegistSttusCode())
+ .colorNm(record.getColorNm())
+ .mrtgCo(record.getMrtgCo())
+ .seizrCo(record.getSeizrCo())
+ .stmdCo(record.getStmdCo())
+ .nmplCsdyAt(record.getNmplCsdyAt())
+ .nmplCsdyRemnrDe(record.getNmplCsdyRemnrDe())
+ .originSeCode(record.getOriginSeCode())
+ .nmplStndrdCode(record.getNmplStndrdCode())
+ .acqsAmount(record.getAcqsAmount())
+ .insptValidPdBgnde(record.getInsptValidPdBgnde())
+ .insptValidPdEndde(record.getInsptValidPdEndde())
+ .useStrnghldGrcCode(record.getUseStrnghldGrcCode())
+ .tkcarPscapCo(record.getTkcarPscapCo())
+ .spmnno(record.getSpmnno())
+ .trvlDstnc(record.getTrvlDstnc())
+ .frstRegistRqrcno(record.getFrstRegistRqrcno())
+ .vlntErsrPrvntcNticeDe(record.getVlntErsrPrvntcNticeDe())
+ .registInsttNm(record.getRegistInsttNm())
+ .processImprtyResnCode(record.getProcessImprtyResnCode())
+ .processImprtyResnDtls(record.getProcessImprtyResnDtls())
+ .cbdLt(record.getCbdLt())
+ .cbdBt(record.getCbdBt())
+ .cbdHg(record.getCbdHg())
+ .frstMxmmLdg(record.getFrstMxmmLdg())
+ .fuelCnsmpRt(record.getFuelCnsmpRt())
+ .elctyCmpndFuelCnsmpRt(record.getElctyCmpndFuelCnsmpRt());
+ }
+
+ /**
+ * 에러 정보를 DB에 업데이트
+ *
+ * @param id 저장된 ID
+ * @param exception 발생한 예외
+ */
+ private void updateErrorLog(String id, Exception exception) {
+ try {
+ CarBassMatterInqireVO errorLog = CarBassMatterInqireVO.builder()
+ .carBassMatterInqire(id)
+ .cntcResultCode("99")
+ .cntcResultDtls("오류: " + exception.getMessage())
+ .build();
+
+ carBassMatterInqireService.updateResponse(errorLog);
+ log.error("[BASIC-ERR-LOG] API 호출 에러 정보 저장 완료 - ID: {}", id, exception);
+ } catch (Exception e) {
+ log.error("[BASIC-ERR-LOG] 에러 로그 저장 실패 - ID: {}", id, e);
+ }
+ }
}
diff --git a/src/main/java/com/vmis/interfaceapp/config/DatabaseConfig.java b/src/main/java/com/vmis/interfaceapp/config/DatabaseConfig.java
new file mode 100644
index 0000000..d6a1d66
--- /dev/null
+++ b/src/main/java/com/vmis/interfaceapp/config/DatabaseConfig.java
@@ -0,0 +1,57 @@
+package com.vmis.interfaceapp.config;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import javax.sql.DataSource;
+
+/**
+ * 데이터베이스 및 트랜잭션 설정
+ *
+ * 이 클래스는 데이터베이스 연결과 트랜잭션 관리를 위한 설정을 제공합니다.
+ * Spring Boot의 자동 설정을 활용하되, 명시적인 트랜잭션 관리를 위해
+ * TransactionManager를 직접 설정합니다.
+ *
+ *
+ * DataSource: application.yml에서 자동 설정
+ * SqlSessionFactory: MyBatis Spring Boot Starter가 자동 생성
+ * TransactionManager: 명시적으로 설정하여 트랜잭션 관리
+ * MapperScan: com.vmis.interfaceapp.mapper 패키지의 Mapper 인터페이스 자동 스캔
+ *
+ */
+@Configuration
+@EnableTransactionManagement
+@MapperScan("com.vmis.interfaceapp.mapper")
+public class DatabaseConfig {
+
+ /**
+ * 트랜잭션 관리자를 설정합니다.
+ *
+ * DataSourceTransactionManager는 JDBC 기반의 트랜잭션을 관리합니다.
+ * @Transactional 어노테이션을 사용하여 선언적 트랜잭션 관리가 가능합니다.
+ *
+ * 트랜잭션 전파(Propagation), 격리 수준(Isolation), 타임아웃 등의
+ * 세부 설정은 @Transactional 어노테이션의 속성으로 지정할 수 있습니다.
+ *
+ * 예제:
+ *
+ * {@code
+ * @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
+ * public void saveData() {
+ * // 트랜잭션 처리가 필요한 로직
+ * }
+ * }
+ *
+ *
+ * @param dataSource Spring Boot가 자동 생성한 DataSource 빈
+ * @return PlatformTransactionManager 트랜잭션 관리자 인스턴스
+ */
+ @Bean
+ public PlatformTransactionManager transactionManager(DataSource dataSource) {
+ return new DataSourceTransactionManager(dataSource);
+ }
+}
diff --git a/src/main/java/com/vmis/interfaceapp/config/Globals.java b/src/main/java/com/vmis/interfaceapp/config/Globals.java
new file mode 100644
index 0000000..d175beb
--- /dev/null
+++ b/src/main/java/com/vmis/interfaceapp/config/Globals.java
@@ -0,0 +1,39 @@
+package com.vmis.interfaceapp.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+/**
+ * 전역 상수 및 설정값을 관리하는 클래스
+ *
+ * 이 클래스는 애플리케이션 전반에서 사용되는 전역 설정값을 제공합니다.
+ * Spring의 @Value를 통해 application.yml의 설정값을 주입받아 사용합니다.
+ */
+@Component
+public class Globals {
+
+ /**
+ * 데이터베이스 타입 (예: mariadb, oracle, mysql 등)
+ *
+ * MyBatis Mapper XML 파일 선택 시 사용됩니다.
+ * mapper-locations: classpath:mybatis/mapper/**\/*_${Globals.DbType}.xml
+ *
+ * 예시:
+ *
+ * DbType = "mariadb" → user_mariadb.xml 매핑
+ * DbType = "oracle" → user_oracle.xml 매핑
+ *
+ *
+ */
+ public static String DbType;
+
+ /**
+ * application.yml에서 Globals.DbType 값을 주입합니다.
+ *
+ * @param dbType 데이터베이스 타입 (기본값: mariadb)
+ */
+ @Value("${Globals.DbType:mariadb}")
+ public void setDbType(String dbType) {
+ Globals.DbType = dbType;
+ }
+}
diff --git a/src/main/java/com/vmis/interfaceapp/mapper/CarBassMatterInqireMapper.java b/src/main/java/com/vmis/interfaceapp/mapper/CarBassMatterInqireMapper.java
new file mode 100644
index 0000000..e192e25
--- /dev/null
+++ b/src/main/java/com/vmis/interfaceapp/mapper/CarBassMatterInqireMapper.java
@@ -0,0 +1,54 @@
+package com.vmis.interfaceapp.mapper;
+
+import com.vmis.interfaceapp.model.basic.CarBassMatterInqireVO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 자동차 기본 사항 조회 Mapper
+ *
+ * API 호출 정보를 관리하는 Mapper 인터페이스입니다.
+ *
+ * 최초 요청 시: insertCarBassMatterInqire() 호출
+ * 결과 수신 시: updateCarBassMatterInqire() 호출
+ *
+ */
+@Mapper
+public interface CarBassMatterInqireMapper {
+
+ /**
+ * 시퀀스로 새로운 자동차 기본 사항 조회 ID를 생성합니다.
+ *
+ * 형식: CBMI000000000001
+ *
+ * @return 생성된 ID
+ */
+ String selectNextCarBassMatterInqireId();
+
+ /**
+ * 최초 API 요청 정보를 등록합니다.
+ *
+ * 요청 시점의 정보만 저장하며, 응답 정보는 null 상태입니다.
+ *
+ * @param carBassMatterInqireVO 요청 정보
+ * @return 등록된 행 수
+ */
+ int insertCarBassMatterInqire(CarBassMatterInqireVO carBassMatterInqireVO);
+
+ /**
+ * API 응답 결과를 업데이트합니다.
+ *
+ * 응답 받은 데이터를 기존 레코드에 업데이트합니다.
+ *
+ * @param carBassMatterInqireVO 응답 정보 (carBassMatterInqire 필드는 필수)
+ * @return 업데이트된 행 수
+ */
+ int updateCarBassMatterInqire(CarBassMatterInqireVO carBassMatterInqireVO);
+
+ /**
+ * ID로 조회 정보를 조회합니다.
+ *
+ * @param carBassMatterInqire 자동차 기본 사항 조회 ID
+ * @return 조회된 정보
+ */
+ CarBassMatterInqireVO selectCarBassMatterInqireById(String carBassMatterInqire);
+}
diff --git a/src/main/java/com/vmis/interfaceapp/mapper/SampleMapper.java b/src/main/java/com/vmis/interfaceapp/mapper/SampleMapper.java
new file mode 100644
index 0000000..528aa2c
--- /dev/null
+++ b/src/main/java/com/vmis/interfaceapp/mapper/SampleMapper.java
@@ -0,0 +1,26 @@
+package com.vmis.interfaceapp.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 샘플 MyBatis Mapper 인터페이스
+ *
+ * 이 인터페이스는 MyBatis 설정 테스트 및 예제 용도입니다.
+ * 실제 프로젝트에서는 비즈니스 요구사항에 맞는 Mapper를 작성하세요.
+ *
+ * @Mapper 어노테이션을 사용하면 Spring이 자동으로 구현체를 생성합니다.
+ * XML 파일의 namespace와 이 인터페이스의 경로가 일치해야 합니다.
+ */
+@Mapper
+public interface SampleMapper {
+
+ /**
+ * 데이터베이스 연결 테스트용 쿼리
+ *
+ * 현재 시간을 조회하여 데이터베이스 연결이 정상적으로
+ * 작동하는지 확인할 수 있습니다.
+ *
+ * @return 현재 시간 문자열
+ */
+ String selectCurrentTime();
+}
diff --git a/src/main/java/com/vmis/interfaceapp/model/basic/CarBassMatterInqireVO.java b/src/main/java/com/vmis/interfaceapp/model/basic/CarBassMatterInqireVO.java
new file mode 100644
index 0000000..53e7838
--- /dev/null
+++ b/src/main/java/com/vmis/interfaceapp/model/basic/CarBassMatterInqireVO.java
@@ -0,0 +1,524 @@
+package com.vmis.interfaceapp.model.basic;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+
+/**
+ * 자동차 기본 사항 조회 엔티티
+ *
+ * API 호출 정보를 저장하는 테이블 매핑 클래스입니다.
+ * 최초 요청 시 INSERT, 결과 수신 시 UPDATE 형태로 사용됩니다.
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CarBassMatterInqireVO {
+
+ /**
+ * 자동차 기본 사항 조회 ID (PK)
+ * 형식: CBMI000000000001
+ */
+ private String carBassMatterInqire;
+
+ // ===== 요청 정보 =====
+ /**
+ * 정보 시스템 ID
+ */
+ private String infoSysId;
+
+ /**
+ * 정보 시스템 IP
+ */
+ private String infoSysIp;
+
+ /**
+ * 시군구 코드
+ */
+ private String sigunguCode;
+
+ /**
+ * 연계 정보 코드
+ */
+ private String cntcInfoCode;
+
+ /**
+ * 담당자 ID
+ */
+ private String chargerId;
+
+ /**
+ * 담당자 IP
+ */
+ private String chargerIp;
+
+ /**
+ * 담당자명
+ */
+ private String chargerNm;
+
+ /**
+ * 요청 부과 기준일
+ */
+ private String dmndLevyStdde;
+
+ /**
+ * 요청 조회 구분 코드
+ */
+ private String dmndInqireSeCode;
+
+ /**
+ * 요청 자동차등록번호
+ */
+ private String dmndVhrno;
+
+ /**
+ * 요청 차대번호
+ */
+ private String dmndVin;
+
+ // ===== 응답 정보 (결과 수신 시 UPDATE) =====
+ /**
+ * 연계 결과 코드
+ */
+ private String cntcResultCode;
+
+ /**
+ * 연계 결과 상세
+ */
+ private String cntcResultDtls;
+
+ /**
+ * 연식
+ */
+ private String prye;
+
+ /**
+ * 등록일
+ */
+ private String registDe;
+
+ /**
+ * 말소 등록 구분 코드
+ */
+ private String ersrRegistSeCode;
+
+ /**
+ * 말소 등록 구분명
+ */
+ private String ersrRegistSeNm;
+
+ /**
+ * 말소 등록일
+ */
+ private String ersrRegistDe;
+
+ /**
+ * 등록 상세 코드
+ */
+ private String registDetailCode;
+
+ /**
+ * 배기량
+ */
+ private String dsplvl;
+
+ /**
+ * 사용 본거지 법정동 코드
+ */
+ private String useStrnghldLegaldongCode;
+
+ /**
+ * 사용 본거지 행정동 코드
+ */
+ private String useStrnghldAdstrdCode;
+
+ /**
+ * 사용 본거지 산
+ */
+ private String useStrnghldMntn;
+
+ /**
+ * 사용 본거지 번지
+ */
+ private String useStrnghldLnbr;
+
+ /**
+ * 사용 본거지 호
+ */
+ private String useStrnghldHo;
+
+ /**
+ * 사용 본거지 상세주소
+ */
+ private String useStrnghldAdresNm;
+
+ /**
+ * 사용 본거지 도로명 코드
+ */
+ private String useStrnghldRoadNmCode;
+
+ /**
+ * 사용 본거지 지하 건물 구분 코드
+ */
+ private String usgsrhldUndgrndBuldSeCode;
+
+ /**
+ * 사용 본거지 건물 주요 번호
+ */
+ private String useStrnghldBuldMainNo;
+
+ /**
+ * 사용 본거지 건물 부 번호
+ */
+ private String useStrnghldBuldSubNo;
+
+ /**
+ * 사용 본거지 전체주소
+ */
+ private String usgsrhldAdresFull;
+
+ /**
+ * 대표소유자 회원 구분 코드
+ */
+ private String mberSeCode;
+
+ /**
+ * 대표소유자 회원 번호
+ */
+ private String mberSeNo;
+
+ /**
+ * 대표소유자 전화번호
+ */
+ private String telno;
+
+ /**
+ * 소유자 법정동 코드
+ */
+ private String ownerLegaldongCode;
+
+ /**
+ * 소유자 행정동 코드
+ */
+ private String ownerAdstrdCode;
+
+ /**
+ * 소유자 산
+ */
+ private String ownerMntn;
+
+ /**
+ * 소유자 번지
+ */
+ private String ownerLnbr;
+
+ /**
+ * 소유자 호
+ */
+ private String ownerHo;
+
+ /**
+ * 소유자 상세주소
+ */
+ private String ownerAdresNm;
+
+ /**
+ * 소유자 도로명 코드
+ */
+ private String ownerRoadNmCode;
+
+ /**
+ * 소유자 지하건물 구분 코드
+ */
+ private String ownerUndgrndBuldSeCode;
+
+ /**
+ * 소유자 건물 주요 번호
+ */
+ private String ownerBuldMainNo;
+
+ /**
+ * 소유자 건물 부 번호
+ */
+ private String ownerBuldSubNo;
+
+ /**
+ * 소유자 전체주소
+ */
+ private String ownrWholaddr;
+
+ /**
+ * 신 차량번호
+ */
+ private String aftrVhrno;
+
+ /**
+ * 사용 연료 코드
+ */
+ private String useFuelCode;
+
+ /**
+ * 용도 구분 코드
+ */
+ private String prposSeCode;
+
+ /**
+ * 원동기 형식명
+ */
+ private String mtrsFomNm;
+
+ /**
+ * 이전 차량번호
+ */
+ private String frntVhrno;
+
+ /**
+ * 차량번호
+ */
+ private String vhclno;
+
+ /**
+ * 차대번호
+ */
+ private String vin;
+
+ /**
+ * 차명
+ */
+ private String cnm;
+
+ /**
+ * 차량 총 중량
+ */
+ private String vhcleTotWt;
+
+ /**
+ * 차령 만료일자
+ */
+ private String caagEndde;
+
+ /**
+ * 차번호 변경시기
+ */
+ private String changeDe;
+
+ /**
+ * 차종 종별 코드
+ */
+ private String vhctyAsortCode;
+
+ /**
+ * 차종 유형 코드
+ */
+ private String vhctyTyCode;
+
+ /**
+ * 차종 분류 코드
+ */
+ private String vhctySeCode;
+
+ /**
+ * 최대 적재량
+ */
+ private String mxmmLdg;
+
+ /**
+ * 차종 종별명
+ */
+ private String vhctyAsortNm;
+
+ /**
+ * 차종 유형명
+ */
+ private String vhctyTyNm;
+
+ /**
+ * 차종 분류명
+ */
+ private String vhctySeNm;
+
+ /**
+ * 최초 등록일
+ */
+ private String frstRegistDe;
+
+ /**
+ * 형식
+ */
+ private String fomNm;
+
+ /**
+ * 취득 일자
+ */
+ private String acqsDe;
+
+ /**
+ * 취득 종료일자
+ */
+ private String acqsEndDe;
+
+ /**
+ * 제작 년월일
+ */
+ private String yblMd;
+
+ /**
+ * 이전 등록일
+ */
+ private String transrRegistDe;
+
+ /**
+ * 제원 등록 상태 코드
+ */
+ private String spcfRegistSttusCode;
+
+ /**
+ * 색상명
+ */
+ private String colorNm;
+
+ /**
+ * 저당수
+ */
+ private String mrtgCo;
+
+ /**
+ * 압류건수
+ */
+ private String seizrCo;
+
+ /**
+ * 구조변경수
+ */
+ private String stmdCo;
+
+ /**
+ * 번호판 영치 여부
+ */
+ private String nmplCsdyAt;
+
+ /**
+ * 번호판 영치 최고일
+ */
+ private String nmplCsdyRemnrDe;
+
+ /**
+ * 출처 구분 코드
+ */
+ private String originSeCode;
+
+ /**
+ * 번호판 규격 코드
+ */
+ private String nmplStndrdCode;
+
+ /**
+ * 취득 금액
+ */
+ private String acqsAmount;
+
+ /**
+ * 검사 유효 기간 시작일
+ */
+ private String insptValidPdBgnde;
+
+ /**
+ * 검사 유효 기간 종료일
+ */
+ private String insptValidPdEndde;
+
+ /**
+ * 사용 본거지 관청 코드
+ */
+ private String useStrnghldGrcCode;
+
+ /**
+ * 승차정원수
+ */
+ private String tkcarPscapCo;
+
+ /**
+ * 제원관리번호
+ */
+ private String spmnno;
+
+ /**
+ * 주행거리
+ */
+ private String trvlDstnc;
+
+ /**
+ * 최초 등록 접수번호
+ */
+ private String frstRegistRqrcno;
+
+ /**
+ * 예고통지일
+ */
+ private String vlntErsrPrvntcNticeDe;
+
+ /**
+ * 등록 기관명
+ */
+ private String registInsttNm;
+
+ /**
+ * 처리 불가 사유 코드
+ */
+ private String processImprtyResnCode;
+
+ /**
+ * 처리 불가 사유 명세
+ */
+ private String processImprtyResnDtls;
+
+ /**
+ * 차체 길이
+ */
+ private String cbdLt;
+
+ /**
+ * 차체 너비
+ */
+ private String cbdBt;
+
+ /**
+ * 차체 높이
+ */
+ private String cbdHg;
+
+ /**
+ * 최초 최대 적재량
+ */
+ private String frstMxmmLdg;
+
+ /**
+ * 연료 소비율
+ */
+ private String fuelCnsmpRt;
+
+ /**
+ * 전기 복합 연료 소비율
+ */
+ private String elctyCmpndFuelCnsmpRt;
+
+ /**
+ * 등록 일시
+ */
+ private LocalDateTime regDt;
+
+ /**
+ * 등록자
+ */
+ private String rgtr;
+
+ /**
+ * 대표소유자 성명
+ */
+ private String mberNm;
+}
diff --git a/src/main/java/com/vmis/interfaceapp/service/CarBassMatterInqireService.java b/src/main/java/com/vmis/interfaceapp/service/CarBassMatterInqireService.java
new file mode 100644
index 0000000..f69f410
--- /dev/null
+++ b/src/main/java/com/vmis/interfaceapp/service/CarBassMatterInqireService.java
@@ -0,0 +1,126 @@
+package com.vmis.interfaceapp.service;
+
+import com.vmis.interfaceapp.mapper.CarBassMatterInqireMapper;
+import com.vmis.interfaceapp.model.basic.CarBassMatterInqireVO;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * 자동차 기본 사항 조회 서비스
+ *
+ * API 호출 정보를 관리하는 서비스 클래스입니다.
+ *
+ * 최초 요청: createInitialRequest() - 시퀀스로 ID 생성 후 INSERT
+ * 결과 업데이트: updateResponse() - 응답 데이터 UPDATE
+ *
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class CarBassMatterInqireService {
+
+ private final CarBassMatterInqireMapper carBassMatterInqireMapper;
+
+ /**
+ * 최초 API 요청 정보를 등록합니다.
+ *
+ * 시퀀스로 새로운 ID를 생성하여 요청 정보를 저장합니다.
+ * 트랜잭션 내에서 실행되며, 예외 발생 시 롤백됩니다.
+ *
+ * 사용 예시:
+ *
+ * {@code
+ * CarBassMatterInqire request = CarBassMatterInqire.builder()
+ * .infoSysId("41-345")
+ * .infoSysIp("105.19.10.135")
+ * .sigunguCode("41460")
+ * .cntcInfoCode("AC1_FD11_01")
+ * .dmndVhrno("12가3456")
+ * .rgtr("SYSTEM")
+ * .build();
+ *
+ * String generatedId = service.createInitialRequest(request);
+ * log.info("생성된 ID: {}", generatedId);
+ * }
+ *
+ *
+ * @param request 요청 정보 (ID 제외)
+ * @return 생성된 자동차 기본 사항 조회 ID (CBMI000000000001 형식)
+ * @throws RuntimeException 저장 실패 시
+ */
+ @Transactional
+ public String createInitialRequest(CarBassMatterInqireVO request) {
+ // 시퀀스로 새로운 ID 생성
+ String generatedId = carBassMatterInqireMapper.selectNextCarBassMatterInqireId();
+ log.debug("생성된 자동차 기본 사항 조회 ID: {}", generatedId);
+
+ // 생성된 ID 설정
+ request.setCarBassMatterInqire(generatedId);
+
+ // INSERT
+ int result = carBassMatterInqireMapper.insertCarBassMatterInqire(request);
+ if (result != 1) {
+ throw new RuntimeException("자동차 기본 사항 조회 정보 등록 실패");
+ }
+
+ log.info("자동차 기본 사항 조회 정보 등록 완료 - ID: {}, 차량번호: {}, 차대번호: {}",
+ generatedId, request.getDmndVhrno(), request.getDmndVin());
+
+ return generatedId;
+ }
+
+ /**
+ * API 응답 결과를 업데이트합니다.
+ *
+ * 기존 레코드에 응답 데이터를 업데이트합니다.
+ * null이 아닌 필드만 업데이트되며, 트랜잭션 내에서 실행됩니다.
+ *
+ * 사용 예시:
+ *
+ * {@code
+ * CarBassMatterInqire response = CarBassMatterInqire.builder()
+ * .carBassMatterInqire("CBMI000000000001")
+ * .cntcResultCode("00")
+ * .cntcResultDtls("성공")
+ * .vhclno("12가3456")
+ * .vin("KMHXX00XXXX000000")
+ * .cnm("소나타")
+ * // ... 기타 응답 필드
+ * .build();
+ *
+ * service.updateResponse(response);
+ * }
+ *
+ *
+ * @param response 응답 정보 (carBassMatterInqire 필드 필수)
+ * @throws RuntimeException 업데이트 실패 시 (레코드가 없는 경우 포함)
+ */
+ @Transactional
+ public void updateResponse(CarBassMatterInqireVO response) {
+ if (response.getCarBassMatterInqire() == null) {
+ throw new IllegalArgumentException("자동차 기본 사항 조회 ID는 필수입니다.");
+ }
+
+ int result = carBassMatterInqireMapper.updateCarBassMatterInqire(response);
+ if (result != 1) {
+ throw new RuntimeException("자동차 기본 사항 조회 정보 업데이트 실패 - ID: " + response.getCarBassMatterInqire());
+ }
+
+ log.info("자동차 기본 사항 조회 정보 업데이트 완료 - ID: {}, 결과코드: {}, 차량번호: {}",
+ response.getCarBassMatterInqire(), response.getCntcResultCode(), response.getVhclno());
+ }
+
+ /**
+ * ID로 조회 정보를 조회합니다.
+ *
+ * @param carBassMatterInqire 자동차 기본 사항 조회 ID
+ * @return 조회된 정보 (없으면 null)
+ */
+ @Transactional(readOnly = true)
+ public CarBassMatterInqireVO getById(String carBassMatterInqire) {
+ return carBassMatterInqireMapper.selectCarBassMatterInqireById(carBassMatterInqire);
+ }
+
+}
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
index d496d2f..69b9ba3 100644
--- a/src/main/resources/application-dev.yml
+++ b/src/main/resources/application-dev.yml
@@ -1,6 +1,44 @@
server:
port: 8080
+spring:
+ # DataSource 설정 - MariaDB
+ datasource:
+ driver-class-name: org.mariadb.jdbc.Driver
+ url: jdbc:mariadb://211.119.124.117:53306/ibmsdb?characterEncoding=UTF-8&allowMultiQueries=true
+ username: root
+ password: xit5811807
+ hikari:
+ # 커넥션 풀 크기 설정 (4코어 32GB 서버 기준)
+ # 동시에 사용할 수 있는 최대 커넥션 수
+ # 권장값: (코어수 × 2) + (동시사용자 × 0.1) = (4 × 2) + (300 × 0.1) = 38 → 40
+ maximum-pool-size: 40
+ # 풀에서 유지할 최소 유휴 커넥션 수
+ # 권장값: maximum-pool-size의 25% (40 × 0.25 = 10)
+ minimum-idle: 10
+ # 커넥션을 얻기 위한 최대 대기 시간 (밀리초)
+ # 권장값: 30초 - 네트워크 지연이나 데이터베이스 부하 시 적절한 대기 시간
+ connection-timeout: 30000
+ # 커넥션 유효성 검사 타임아웃 (밀리초)
+ validation-timeout: 60000
+ # 커넥션의 최대 생명 시간 (밀리초)
+ # 권장값: 30분 - 데이터베이스 연결이 너무 오래 유지되지 않도록 제한
+ max-lifetime: 1800000
+ # 유휴 커넥션을 제거하기 위한 최소 대기 시간 (밀리초)
+ # 권장값: 10분 - 메모리 절약과 커넥션 재사용의 균형점
+ idle-timeout: 600000
+ # auto-commit을 false로 설정하여 명시적 트랜잭션 관리
+ auto-commit: false
+
+# MyBatis 설정
+mybatis:
+ # MyBatis 전역 설정 파일 위치
+ config-location: classpath:mybatis/mybatis-config.xml
+ # Mapper XML 파일 위치 (DbType 변수 사용)
+ mapper-locations: classpath:mybatis/mapper/**/*_${Globals.DbType}.xml
+ # 타입 별칭 패키지 (하위 패키지 자동 스캔)
+ type-aliases-package: com.vmis.interfaceapp.model
+
# 로그 설정 - 개발(DEV) 환경
logging:
config: classpath:logback-spring.xml
diff --git a/src/main/resources/application-prd.yml b/src/main/resources/application-prd.yml
index e5983b5..9ac423d 100644
--- a/src/main/resources/application-prd.yml
+++ b/src/main/resources/application-prd.yml
@@ -1,6 +1,44 @@
server:
port: 8080
+spring:
+ # DataSource 설정 - MariaDB
+ datasource:
+ driver-class-name: org.mariadb.jdbc.Driver
+ url: jdbc:mariadb://211.119.124.117:53306/ibmsdb?characterEncoding=UTF-8&allowMultiQueries=true
+ username: root
+ password: xit5811807
+ hikari:
+ # 커넥션 풀 크기 설정 (4코어 32GB 서버 기준)
+ # 동시에 사용할 수 있는 최대 커넥션 수
+ # 권장값: (코어수 × 2) + (동시사용자 × 0.1) = (4 × 2) + (300 × 0.1) = 38 → 40
+ maximum-pool-size: 40
+ # 풀에서 유지할 최소 유휴 커넥션 수
+ # 권장값: maximum-pool-size의 25% (40 × 0.25 = 10)
+ minimum-idle: 10
+ # 커넥션을 얻기 위한 최대 대기 시간 (밀리초)
+ # 권장값: 30초 - 네트워크 지연이나 데이터베이스 부하 시 적절한 대기 시간
+ connection-timeout: 30000
+ # 커넥션 유효성 검사 타임아웃 (밀리초)
+ validation-timeout: 60000
+ # 커넥션의 최대 생명 시간 (밀리초)
+ # 권장값: 30분 - 데이터베이스 연결이 너무 오래 유지되지 않도록 제한
+ max-lifetime: 1800000
+ # 유휴 커넥션을 제거하기 위한 최소 대기 시간 (밀리초)
+ # 권장값: 10분 - 메모리 절약과 커넥션 재사용의 균형점
+ idle-timeout: 600000
+ # auto-commit을 false로 설정하여 명시적 트랜잭션 관리
+ auto-commit: false
+
+# MyBatis 설정
+mybatis:
+ # MyBatis 전역 설정 파일 위치
+ config-location: classpath:mybatis/mybatis-config.xml
+ # Mapper XML 파일 위치 (DbType 변수 사용)
+ mapper-locations: classpath:mybatis/mapper/**/*_${Globals.DbType}.xml
+ # 타입 별칭 패키지 (하위 패키지 자동 스캔)
+ type-aliases-package: com.vmis.interfaceapp.model
+
# 로그 설정 - 운영(PRD) 환경
logging:
config: classpath:logback-spring.xml
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 0718988..97c8ccb 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -5,5 +5,9 @@ spring:
pathmatch:
matching-strategy: ant_path_matcher
+# 전역 설정
+Globals:
+ DbType: maria
+
server:
port: 8080
\ No newline at end of file
diff --git a/src/main/resources/mybatis/mapper/CarBassMatterInqireMapper_maria.xml b/src/main/resources/mybatis/mapper/CarBassMatterInqireMapper_maria.xml
new file mode 100644
index 0000000..cc6dd0b
--- /dev/null
+++ b/src/main/resources/mybatis/mapper/CarBassMatterInqireMapper_maria.xml
@@ -0,0 +1,148 @@
+
+
+
+
+
+
+
+ SELECT CONCAT('CBMI', LPAD(NEXTVAL(seq_car_bass_matter_inqire), 16, '0')) AS id
+
+
+
+
+ INSERT INTO tb_car_bass_matter_inqire (
+ CAR_BASS_MATTER_INQIRE,
+ INFO_SYS_ID,
+ INFO_SYS_IP,
+ SIGUNGU_CODE,
+ CNTC_INFO_CODE,
+ CHARGER_ID,
+ CHARGER_IP,
+ CHARGER_NM,
+ DMND_LEVY_STDDE,
+ DMND_INQIRE_SE_CODE,
+ DMND_VHRNO,
+ DMND_VIN,
+ REG_DT,
+ RGTR
+ ) VALUES (
+ #{carBassMatterInqire},
+ #{infoSysId},
+ #{infoSysIp},
+ #{sigunguCode},
+ #{cntcInfoCode},
+ #{chargerId},
+ #{chargerIp},
+ #{chargerNm},
+ #{dmndLevyStdde},
+ #{dmndInqireSeCode},
+ #{dmndVhrno},
+ #{dmndVin},
+ NOW(),
+ #{rgtr}
+ )
+
+
+
+
+ UPDATE tb_car_bass_matter_inqire
+
+ CNTC_RESULT_CODE = #{cntcResultCode},
+ CNTC_RESULT_DTLS = #{cntcResultDtls},
+ PRYE = #{prye},
+ REGIST_DE = #{registDe},
+ ERSR_REGIST_SE_CODE = #{ersrRegistSeCode},
+ ERSR_REGIST_SE_NM = #{ersrRegistSeNm},
+ ERSR_REGIST_DE = #{ersrRegistDe},
+ REGIST_DETAIL_CODE = #{registDetailCode},
+ DSPLVL = #{dsplvl},
+ USE_STRNGHLD_LEGALDONG_CODE = #{useStrnghldLegaldongCode},
+ USE_STRNGHLD_ADSTRD_CODE = #{useStrnghldAdstrdCode},
+ USE_STRNGHLD_MNTN = #{useStrnghldMntn},
+ USE_STRNGHLD_LNBR = #{useStrnghldLnbr},
+ USE_STRNGHLD_HO = #{useStrnghldHo},
+ USE_STRNGHLD_ADRES_NM = #{useStrnghldAdresNm},
+ USE_STRNGHLD_ROAD_NM_CODE = #{useStrnghldRoadNmCode},
+ USGSRHLD_UNDGRND_BULD_SE_CODE = #{usgsrhldUndgrndBuldSeCode},
+ USE_STRNGHLD_BULD_MAIN_NO = #{useStrnghldBuldMainNo},
+ USE_STRNGHLD_BULD_SUB_NO = #{useStrnghldBuldSubNo},
+ USGSRHLD_ADRES_FULL = #{usgsrhldAdresFull},
+ MBER_SE_CODE = #{mberSeCode},
+ MBER_SE_NO = #{mberSeNo},
+ TELNO = #{telno},
+ OWNER_LEGALDONG_CODE = #{ownerLegaldongCode},
+ OWNER_ADSTRD_CODE = #{ownerAdstrdCode},
+ OWNER_MNTN = #{ownerMntn},
+ OWNER_LNBR = #{ownerLnbr},
+ OWNER_HO = #{ownerHo},
+ OWNER_ADRES_NM = #{ownerAdresNm},
+ OWNER_ROAD_NM_CODE = #{ownerRoadNmCode},
+ OWNER_UNDGRND_BULD_SE_CODE = #{ownerUndgrndBuldSeCode},
+ OWNER_BULD_MAIN_NO = #{ownerBuldMainNo},
+ OWNER_BULD_SUB_NO = #{ownerBuldSubNo},
+ OWNR_WHOLADDR = #{ownrWholaddr},
+ AFTR_VHRNO = #{aftrVhrno},
+ USE_FUEL_CODE = #{useFuelCode},
+ PRPOS_SE_CODE = #{prposSeCode},
+ MTRS_FOM_NM = #{mtrsFomNm},
+ FRNT_VHRNO = #{frntVhrno},
+ VHCLNO = #{vhclno},
+ VIN = #{vin},
+ CNM = #{cnm},
+ VHCLE_TOT_WT = #{vhcleTotWt},
+ CAAG_ENDDE = #{caagEndde},
+ CHANGE_DE = #{changeDe},
+ VHCTY_ASORT_CODE = #{vhctyAsortCode},
+ VHCTY_TY_CODE = #{vhctyTyCode},
+ VHCTY_SE_CODE = #{vhctySeCode},
+ MXMM_LDG = #{mxmmLdg},
+ VHCTY_ASORT_NM = #{vhctyAsortNm},
+ VHCTY_TY_NM = #{vhctyTyNm},
+ VHCTY_SE_NM = #{vhctySeNm},
+ FRST_REGIST_DE = #{frstRegistDe},
+ FOM_NM = #{fomNm},
+ ACQS_DE = #{acqsDe},
+ ACQS_END_DE = #{acqsEndDe},
+ YBL_MD = #{yblMd},
+ TRANSR_REGIST_DE = #{transrRegistDe},
+ SPCF_REGIST_STTUS_CODE = #{spcfRegistSttusCode},
+ COLOR_NM = #{colorNm},
+ MRTG_CO = #{mrtgCo},
+ SEIZR_CO = #{seizrCo},
+ STMD_CO = #{stmdCo},
+ NMPL_CSDY_AT = #{nmplCsdyAt},
+ NMPL_CSDY_REMNR_DE = #{nmplCsdyRemnrDe},
+ ORIGIN_SE_CODE = #{originSeCode},
+ NMPL_STNDRD_CODE = #{nmplStndrdCode},
+ ACQS_AMOUNT = #{acqsAmount},
+ INSPT_VALID_PD_BGNDE = #{insptValidPdBgnde},
+ INSPT_VALID_PD_ENDDE = #{insptValidPdEndde},
+ USE_STRNGHLD_GRC_CODE = #{useStrnghldGrcCode},
+ TKCAR_PSCAP_CO = #{tkcarPscapCo},
+ SPMNNO = #{spmnno},
+ TRVL_DSTNC = #{trvlDstnc},
+ FRST_REGIST_RQRCNO = #{frstRegistRqrcno},
+ VLNT_ERSR_PRVNTC_NTICE_DE = #{vlntErsrPrvntcNticeDe},
+ REGIST_INSTT_NM = #{registInsttNm},
+ PROCESS_IMPRTY_RESN_CODE = #{processImprtyResnCode},
+ PROCESS_IMPRTY_RESN_DTLS = #{processImprtyResnDtls},
+ CBD_LT = #{cbdLt},
+ CBD_BT = #{cbdBt},
+ CBD_HG = #{cbdHg},
+ FRST_MXMM_LDG = #{frstMxmmLdg},
+ FUEL_CNSMP_RT = #{fuelCnsmpRt},
+ ELCTY_CMPND_FUEL_CNSMP_RT = #{elctyCmpndFuelCnsmpRt},
+ MBER_NM = #{mberNm},
+
+ WHERE CAR_BASS_MATTER_INQIRE = #{carBassMatterInqire}
+
+
+
+
+ SELECT *
+ FROM tb_car_bass_matter_inqire
+ WHERE CAR_BASS_MATTER_INQIRE = #{carBassMatterInqire}
+
+
+
diff --git a/src/main/resources/mybatis/mapper/sample_maria.xml b/src/main/resources/mybatis/mapper/sample_maria.xml
new file mode 100644
index 0000000..b1a4c7f
--- /dev/null
+++ b/src/main/resources/mybatis/mapper/sample_maria.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+ SELECT NOW() AS current_time
+
+
+
+
+
diff --git a/src/main/resources/mybatis/mybatis-config.xml b/src/main/resources/mybatis/mybatis-config.xml
new file mode 100644
index 0000000..2f288d1
--- /dev/null
+++ b/src/main/resources/mybatis/mybatis-config.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+