본문 바로가기
백엔드 개발

Spring Boot + SAP JCo 연동 SDK 만들기 (Builder 패턴 적용)

by collenkim 2026. 3. 27.
반응형

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 처리 시 예외 처리 포함

 

주요 구성 요소:

  1. Connection Manager
    • SAP Destination 생성
    • 커넥션 풀 관리
  2. Function Executor
    • RFC 호출 추상화
    • 예외 처리 포함
  3. Batch/Service Client
    • 대량 데이터 처리나 반복 호출 지원
  4. 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 매핑을 통해 함수 결과를 객체 형태로 바로 활용 가능
반응형