반응형
SAP 시스템과 Java 애플리케이션을 연동할 때, 반복되는 연결 설정, 인증, RFC 호출 등은 개발 효율성과 유지보수 측면에서 공통 SDK로 묶어두는 것이 좋습니다. 이번 글에서는 Spring Boot 기반 Java 프로젝트에서 재사용 가능한 SAP 연동 SDK를 Builder 패턴으로 구현하는 방법을 상세히 소개합니다.
1. SAP JCo(Java Connector) 소개
- SAP JCo는 Java에서 SAP 시스템의 RFC(Remote Function Call)를 호출할 수 있도록 지원하는 공식 라이브러리입니다.
- Java 애플리케이션에서 SAP BAPI 호출, IDoc 처리, 데이터 조회를 가능하게 해줍니다.
- 연결 방식:
- 사용자 인증(USER/PASSWD)
- SSO / SNC(Security Network Communication)
실무에서는 프로젝트마다 SAP 시스템 연결 방식이 조금씩 다르기 때문에, 연결 설정을 추상화한 공통 SDK를 만드는 것이 필수적입니다.
2. 공통 모듈 SDK 설계 목표
SDK 설계 시 핵심 목표는 다음과 같습니다.
- 재사용성: 여러 프로젝트에서 동일한 방식으로 SAP 연동
- 안전한 인증: 필수 옵션 검증, 선택 옵션 유연 적용
- 연결 관리: Destination 생성, 커넥션 풀 관리
- 오류 처리: RFC 호출 실패나 Export Parameter 처리 시 예외 처리 포함
주요 구성 요소:
- Connection Manager
- SAP Destination 생성
- 커넥션 풀 관리
- Function Executor
- RFC 호출 추상화
- 예외 처리 포함
- Batch/Service Client
- 대량 데이터 처리나 반복 호출 지원
- Configuration Loader
- 환경별 프로퍼티 관리
- application.properties 또는 외부 config에서 옵션 로드
3. 연동 옵션 정리
SAP JCo를 사용할 때 설정해야 하는 필수/권장 옵션입니다.
| 옵션 | 설명 | 필수 여부 |
| jco.client.client | SAP 클라이언트 번호 | 필수 |
| jco.client.user | 사용자 ID | 필수 |
| jco.client.passwd | 비밀번호 | 필수 |
| jco.client.ashost | Application Server 호스트 | 필수 |
| jco.client.sysnr | 시스템 번호 | 필수 |
| jco.client.lang | 언어 코드 (EN/KO 등) | 필수 |
| jco.client.saprouter | SAP 라우터 경로 | 선택 |
| jco.client.alias_user | 대체 사용자 | 선택 |
| jco.client.mysapsso2 | SSO 티켓 사용 | 선택 |
| jco.client.getsso2 | SSO 자동 요청 여부 | 선택 |
| jco.client.deny_initial_password | 초기 패스워드 사용 금지 | 선택 |
| jco.client.x509cert | 인증서 기반 로그인 | 선택 |
| jco.client.extid_data | 외부 사용자 데이터 | 선택 |
| jco.client.extid_type | 외부 사용자 타입 | 선택 |
| jco.client.r3name | SAP 시스템 이름 | 선택 |
| jco.client.group | 로드밸런싱 그룹 | 선택 |
| jco.client.gwhost | Gateway 호스트 | 선택 |
| jco.client.gwserv | Gateway 서비스 | 선택 |
| jco.client.tphost | 트랜잭션 서버 호스트 | 선택 |
| jco.client.tpname | 트랜잭션 서버 이름 | 선택 |
| jco.client.type | 서버 유형(R/3, Gateway 등) | 선택 |
| jco.client.use_sapgui | SAP GUI 사용 여부 | 선택 |
| jco.destination.pool_capacity | 커넥션 풀 초기 용량 | 선택 |
| jco.destination.peak_limit | 최대 동시 연결 수 | 선택 |
| jco.destination.expiration_time | 커넥션 만료 시간(ms) | 선택 |
| jco.destination.expiration_check_period | 만료 체크 주기(ms) | 선택 |
| jco.destination.max_get_client_time | 최대 동시 연결 | 선택 |
| jco.destination.repository_destination | 리포지토리 대상 이름 | 선택 |
| jco.destination.repository.user | 리포지토리 접속 사용자 | 선택 |
| jco.destination.repository.passwd | 리포지토리 비밀번호 | 선택 |
| jco.destination.repository.snc_mode | 리포지토리 SNC 모드 | 선택 |
| jco.client.cpic_trace | CPIC 통신 트레이스 레벨 | 선택 |
| jco.client.trace | RFC/CPIC 트레이스 활성화 | 선택 |
| jco.client.codepage | 문자 코드 페이지 | 선택 |
| jco.client.pcs | 프로토콜 코드 설정 | 선택 |
| jco.client.lcheck | 로그인 체크 여부 | 선택 |
| jco.client.delta | 델타 전송 사용 여부 | 선택 |
| jco.client.snc_partnername | SNC 파트너 이름 | 선택 |
| jco.client.snc_qop | SNC 품질 수준 | 선택 |
| jco.client.snc_myname | SNC 인증 이름 | 선택 |
| jco.client.snc_mode | SNC 모드 | 선택 |
| jco.client.snc_sso | SNC SSO 사용 여부 | 선택 |
| jco.client.snc_lib | SNC 라이브러리 경로 | 선택 |
4. SapClient 작성 (Builder 패턴 적용)
실무에서 반복되는 SAP JCo 설정, RFC 호출, Import/Export Parameter 처리 등을 공통 SDK로 묶으면 재사용성이 높아집니다.
Builder 패턴을 사용해 필수 옵션을 검증하고, 선택 옵션은 필요할 때만 추가하도록 설계합니다.
SapClient 구현 예제
package com.example.sap.sdk;
import com.sap.conn.jco.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.lang3.StringUtils;
public class SapClient {
private final JCoDestination destination;
private final ObjectMapper objectMapper = new ObjectMapper();
private SapClient(JCoDestination destination) {
this.destination = destination;
}
public static Builder builder() { return new Builder(); }
/** Builder 패턴 */
public static class Builder {
private String client;
private String user;
private String passwd;
private String asHost;
private String sysNr;
private String lang;
public Builder withClient(String client) { this.client = client; return this; }
public Builder withUser(String user) { this.user = user; return this; }
public Builder withPasswd(String passwd) { this.passwd = passwd; return this; }
public Builder withAsHost(String asHost) { this.asHost = asHost; return this; }
public Builder withSysNr(String sysNr) { this.sysNr = sysNr; return this; }
public Builder withLang(String lang) { this.lang = lang; return this; }
public SapClient build() throws SapClientException {
if(StringUtils.isBlank(client) || StringUtils.isBlank(user) || StringUtils.isBlank(passwd)
|| StringUtils.isBlank(asHost) || StringUtils.isBlank(sysNr) || StringUtils.isBlank(lang)) {
throw new SapClientException("필수 SAP 옵션 누락");
}
try {
Properties props = new Properties();
props.setProperty("jco.client.client", client);
props.setProperty("jco.client.user", user);
props.setProperty("jco.client.passwd", passwd);
props.setProperty("jco.client.ashost", asHost);
props.setProperty("jco.client.sysnr", sysNr);
props.setProperty("jco.client.lang", lang);
MyDestinationDataProvider provider = new MyDestinationDataProvider();
provider.addDestination("SAP_DEST", props);
JCoDestination destination = JCoDestinationManager.getDestination("SAP_DEST");
return new SapClient(destination);
} catch(JCoException e) {
throw new SapClientException("SAP 연결 생성 실패", e);
}
}
}
/**
* RFC Function 호출 및 결과 DTO 바인딩
* @param functionName RFC Function 이름
* @param importParams Import Parameter Map
* @param importTables Import Table Parameter Map
* @param responseClass 결과 바인딩 DTO 클래스
*/
public <T> T executeFunction(
String functionName,
Map<String,Object> importParams,
Map<String,Object[]> importTables,
Class<T> responseClass
) throws SapClientException {
try {
JCoFunction function = destination.getRepository().getFunction(functionName);
if(function == null) throw new SapClientException("Function not found: "+functionName);
// Import Parameter 설정
JCoParameterList importList = function.getImportParameterList();
if(importParams != null) importParams.forEach(importList::setValue);
// Import Table Parameter 설정
JCoParameterList tableList = function.getTableParameterList();
if(importTables != null) {
importTables.forEach((tableName, rows) -> {
JCoTable table = tableList.getTable(tableName);
for(Object rowObj : rows) {
table.appendRow();
Map<String,Object> rowMap = objectMapper.convertValue(rowObj, Map.class);
rowMap.forEach(table::setValue);
}
});
}
// Function 실행
function.execute(destination);
// Export Parameter를 DTO로 변환
T result = objectMapper.convertValue(function.getExportParameterList().toStructure(), responseClass);
return result;
} catch(Exception e) {
throw new SapClientException("Function 실행 실패", e);
}
}
}
5. SapClient 사용 예제
Builder 패턴으로 Client를 생성하고, Import/Export Parameter를 Map 또는 객체로 전달하며, 결과를 DTO로 바로 받을 수 있습니다.
1) DTO 클래스 준비
package com.example.sap.dto;
public class MyResponseDto {
private String EMTYPE;
private String EMSG;
public String getEMTYPE() { return EMTYPE; }
public void setEMTYPE(String EMTYPE) { this.EMTYPE = EMTYPE; }
public String getEMSG() { return EMSG; }
public void setEMSG(String EMSG) { this.EMSG = EMSG; }
}
2) SapClient 생성
import com.example.sap.sdk.SapClient;
SapClient sapClient = SapClient.builder()
.withClient("100") // SAP 클라이언트 번호
.withUser("MYUSER") // 사용자 ID
.withPasswd("MYPASS") // 비밀번호
.withAsHost("sap.example.com")// Application Server 호스트
.withSysNr("00") // 시스템 번호
.withLang("EN") // 언어 코드
.build(); // 필수 옵션 검증 후 SapClient 생성
필수 옵션 누락 시 SapClientException 발생
선택 옵션 (withSAPRouter, withAliasUser 등)은 필요 시 추가 가능
Builder 패턴 덕분에 가독성과 재사용성이 향상
이후 sapClient.executeFunction(...) 메서드를 통해 RFC 함수를 호출하고, Import/Export Parameter를 Map 또는 DTO로 주고받을 수 있습니다.
3) Import Parameter 및 Table 설정
import java.util.Map;
// 단일 Import Parameter
Map<String, Object> importParams = Map.of(
"I_TYPE", "A"
);
// Table Parameter (배열 안에 Map 객체)
Map<String, Object[]> importTables = Map.of(
"T_BPKIND", new Object[]{
Map.of(
"SIGN", "I",
"OPTION", "EQ",
"LOW", "BPKIND001",
"HIGH", ""
)
}
);
Import Parameter → Map<String, Object>
Import Table → Map<String, Object []>
각 행(row)은 Map으로 표현
4) Function 호출 및 결과 DTO 바인딩
MyResponseDto response = sapClient.executeFunction(
"Z51CRM100_F003", // RFC Function 이름
importParams, // Import Parameter
importTables, // Import Table Parameter
MyResponseDto.class // 결과 Export Parameter DTO
);
// 결과 확인
System.out.println("EMTYPE : " + response.getEMTYPE());
System.out.println("EMSG : " + response.getEMSG());
반복되는 JCoTable 처리, appendRow, setValue, execute를 SDK에서 공통 처리
Export Parameter를 DTO로 매핑해 바로 사용 가능
모든 RFC 호출을 동일한 방식으로 처리 가능
마무리
- Builder 패턴으로 SAP Client를 구성하면 필수 옵션 검증과 선택 옵션 유연 적용이 가능
- RFC 호출, Import/Export Parameter, Table 처리 로직을 SDK에서 공통화하면 코드 중복 제거 및 유지보수 효율 향상
- DTO 매핑을 통해 함수 결과를 객체 형태로 바로 활용 가능
반응형
'백엔드 개발' 카테고리의 다른 글
| 로컬 캐시(Local Cache), 언제 쓰고 어떻게 써야 할까? (Spring + Caffeine 정리) (0) | 2026.04.01 |
|---|---|
| java.lang.NullPointerException 원인 정리 (0) | 2026.03.31 |
| Spring Boot Redis 실무 활용: 클러스터/싱글 + 동기/비동기 + TTL + LocalDateTime (0) | 2026.02.13 |
| Builder 패턴으로 SDK 모듈 래핑하기 (실무 적용 사례) (0) | 2026.02.09 |
| AWS SQS SDK 기반 Consumer 실무 적용기 (0) | 2026.02.06 |