ABAP

I_HouseBank vs BNKA: CDS로 바꿔야 하는 이유 #shorts #SAP #ABAP

BNKA 테이블의 구조와 한계 — 왜 직접 SELECT하면 안 되는가

SAP에서 은행 관련 데이터를 다뤄본 개발자라면 BNKA(은행 마스터), T012(하우스 뱅크), T012K(하우스 뱅크 계좌) 세 테이블을 직접 SELECT하는 코드를 한 번쯤 봤을 것입니다. BNKA는 은행 국가(BANKS)와 은행 키(BANKL)를 키로 하는 은행 마스터 테이블이고, T012는 회사 코드(BUKRS)별로 하우스 뱅크 ID(HBKID)를 관리하는 테이블입니다. 하우스 뱅크의 은행명이나 주소를 얻으려면 두 테이블을 조인해야 하므로, 수많은 레거시 프로그램에 아래와 같은 패턴이 반복적으로 등장합니다.

" 레거시 패턴 — ABAP Cloud에서는 구문 검사 자체를 통과하지 못한다
SELECT t~bukrs, t~hbkid, t~banks, t~bankl, b~banka
  FROM t012 AS t
  INNER JOIN bnka AS b
    ON  b~banks = t~banks
    AND b~bankl = t~bankl
  INTO TABLE @DATA(lt_old_result).

이 방식에는 세 가지 문제가 있습니다. 첫째, BNKA·T012는 릴리스되지 않은(Not Released) 기술 테이블이라 SAP S/4HANA Cloud Public Edition과 ABAP Cloud 개발 모델에서는 아예 접근이 차단됩니다. 둘째, BANKA, HBKID 같은 독일어 약어 필드명은 코드 가독성을 떨어뜨립니다. 셋째, SAP가 내부 테이블 구조를 변경하면(실제로 S/4HANA 전환 과정에서 여러 FI 테이블이 재편되었습니다) 직접 SELECT한 코드는 그대로 깨집니다. 이 세 문제를 한 번에 해결하는 것이 릴리스된 CDS 뷰 I_HouseBank입니다.

I_HouseBank 설계 배경: VDM(Virtual Data Model) 계층 이해

SAP S/4HANA(On-Premise 2021 이상 및 Cloud 에디션)는 물리 테이블 위에 VDM이라는 의미론적 계층을 얹었습니다. 건물에 비유하면 BNKA·T012 같은 테이블은 배관과 전선이 노출된 지하 설비층이고, VDM은 입주자가 실제로 사용하는 정돈된 사무 공간입니다. 설비층을 직접 건드리면 당장은 동작하지만 건물 리모델링(업그레이드) 때 배관 위치가 바뀌면 모든 것이 무너집니다. VDM 계층은 다음과 같이 나뉩니다.

  • Basic 뷰 (I_*): 물리 테이블을 1:1에 가깝게 감싸되 필드명을 영어 의미명으로 정규화. I_HouseBank, I_Bank, I_HouseBankAccount가 여기에 속합니다.
  • Composite 뷰: 여러 Basic 뷰를 조합해 비즈니스 로직을 추가.
  • Consumption 뷰 (C_*): Fiori 앱·분석 리포트가 직접 소비.

여기서 주의할 점이 하나 있습니다. I_HouseBank의 기반 테이블은 BNKA가 아니라 T012입니다. BNKA(은행 마스터 자체)에 대응하는 릴리스 뷰는 I_Bank입니다. 즉 "BNKA 없이 하우스 뱅크를 조회한다"는 말은, T012+BNKA 수동 조인을 버리고 I_HouseBank와 그 Association _Bank를 통해 은행 마스터 정보까지 선언적으로 가져온다는 뜻입니다. 릴리스 상태(Release Contract C1)는 ADT에서 뷰를 열고 Properties > API State에서 확인할 수 있으며, 릴리스된 뷰는 업그레이드 시 호환성이 유지되도록 관리되는 것이 일반적입니다.

I_HouseBank 필드 구조 완전 분석

S/4HANA 2023 기준 I_HouseBank의 핵심 필드는 다음과 같습니다. 시스템 버전에 따라 세부 구성이 다를 수 있으므로 ADT나 SAP Business Accelerator Hub에서 실제 정의를 확인하는 것을 권장합니다.

CDS 필드원본 필드(T012)의미
CompanyCode (키)BUKRS회사 코드
HouseBank (키)HBKID하우스 뱅크 ID (예: KEB01)
BankCountryBANKS은행 소재 국가/지역
BankBANKL은행 키 (은행 마스터 연결 고리)

그리고 주요 Association은 다음과 같습니다.

  • _BankI_Bank: 은행명(BankName), 지역, SWIFT 코드 등 BNKA에 있던 정보
  • _CompanyCodeI_CompanyCode: 회사 코드 명칭, 통화
  • _BankCountry → 국가 정보 뷰

Association은 "미리 선언해 둔 지연 조인"입니다. 뷰 정의에 조인 경로만 등록해 두고, 실제 SQL 조인은 소비 시점에 해당 경로를 사용할 때만 실행되므로 불필요한 조인 비용이 발생하지 않습니다.

ABAP 코드: BNKA SELECT → I_HouseBank 전환 실전 예제

앞서 본 레거시 조인을 전환하면 이렇게 됩니다. 조인 조건이 통째로 사라지고 PATH 표현식 한 줄로 대체되는 점에 주목하세요.

" 1단계: 기본 전환 — 하우스 뱅크 목록 + 은행명
SELECT FROM I_HouseBank
  FIELDS CompanyCode,
         HouseBank,
         BankCountry,
         Bank,
         \_Bank-BankName AS BankName
  WHERE CompanyCode = '1000'
  ORDER BY HouseBank
  INTO TABLE @DATA(lt_house_banks).

IF sy-subrc <> 0.
  " 하우스 뱅크 미설정 회사 코드 — 후속 로직에서 빈 테이블 처리
ENDIF.

전환 시 매핑 규칙은 단순합니다. BUKRS → CompanyCode, HBKID → HouseBank, BANKS → BankCountry, BANKL → Bank, 그리고 BNKA에서 가져오던 BANKA(은행명)는 \_Bank-BankName으로 바뀝니다. 백슬래시(\)로 시작하는 것이 ABAP SQL에서 Association 경로를 쓰는 문법입니다.

연관 CDS 뷰 조합: I_HouseBankAccount와 함께 쓰기

하우스 뱅크만으로는 실무가 끝나지 않습니다. 실제 지급 처리는 하우스 뱅크 아래의 계좌 ID(T012K의 HKTID) 단위로 이루어지기 때문입니다. T012K에 대응하는 릴리스 뷰가 I_HouseBankAccount이며, 키는 CompanyCode, HouseBank, HouseBankAccount이고 계좌 통화·연결 G/L 계정 등의 필드를 제공합니다. 두 뷰를 조합한 예제입니다.

" 2단계: 하우스 뱅크 + 계좌 + 은행명을 한 번에 조회 (에러 처리 포함)
TYPES: BEGIN OF ty_bank_acct,
         company_code TYPE bukrs,
         house_bank   TYPE hbkid,
         bank_account TYPE hktid,
         bank_name    TYPE string,
       END OF ty_bank_acct.

TRY.
    SELECT FROM I_HouseBankAccount AS acct
      INNER JOIN I_HouseBank AS hb
        ON  hb~CompanyCode = acct~CompanyCode
        AND hb~HouseBank   = acct~HouseBank
      FIELDS acct~CompanyCode      AS company_code,
             acct~HouseBank        AS house_bank,
             acct~HouseBankAccount AS bank_account,
             hb~\_Bank-BankName    AS bank_name
      WHERE acct~CompanyCode = @iv_company_code
      INTO TABLE @DATA(lt_accounts).
  CATCH cx_sy_open_sql_db INTO DATA(lx_db).
    " 실무에서는 Application Log(BAL 또는 Cloud의 로깅 API)에 기록
    RAISE EXCEPTION NEW zcx_bank_read( previous = lx_db ).
ENDTRY.

참고로 I_HouseBankAccount에도 _HouseBank Association이 있어, 조인 없이 \_HouseBank\_Bank-BankName처럼 경로를 이어 쓰는 것도 가능합니다. 명시적 조인과 경로 표현식 중 어느 쪽이든 옵티마이저가 처리하지만, 단순 조회에서는 경로 표현식이 코드량이 적어 일반적으로 선호됩니다.

CDS Association과 PATH 표현식으로 조회 범위 확장

자체 CDS 뷰를 만들 때도 I_HouseBank를 데이터 소스로 삼을 수 있습니다. 아래는 재무팀 리포트용 커스텀 뷰 예시입니다.

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: '결제용 하우스 뱅크 요약'
define view entity ZI_PaymentHouseBank
  as select from I_HouseBank as HouseBank
  association [0..1] to I_CompanyCode as _CoCode
    on $projection.CompanyCode = _CoCode.CompanyCode
{
  key HouseBank.CompanyCode,
  key HouseBank.HouseBank,
      HouseBank.BankCountry,
      HouseBank.Bank,
      HouseBank._Bank.BankName   as HouseBankName,
      HouseBank._Bank.SWIFTCode  as SWIFTCode,
      _CoCode.CompanyCodeName    as CompanyCodeName,

      /* 소비자가 필요할 때 따라갈 수 있도록 Association 노출 */
      HouseBank._Bank,
      _CoCode
}

_Bank.BankName처럼 필드로 꺼내면 즉시 조인이 실행되고, Association 자체를 노출하면 소비 뷰가 선택적으로 따라갈 수 있습니다. "지금 필요한 필드는 꺼내고, 나중에 필요할 수 있는 경로는 노출한다"가 VDM 설계의 기본 원칙입니다. @AccessControl.authorizationCheck: #CHECK를 지정하면 DCL 기반 권한 제어를 붙일 수 있어, 회사 코드 권한(F_BKPF_BUK 계열)을 뷰 레벨에서 걸러내는 구성이 가능합니다.

실무 시나리오: 회사 코드별 결제 하우스 뱅크 조회 프로그램

실제 프로젝트에서 자주 나오는 요구사항인 "지급 실행 전, 회사 코드에 설정된 하우스 뱅크·계좌 현황 점검" 로직을 클래스로 구성한 예입니다. 테스트 가능성을 위해 조회를 인터페이스로 분리했습니다.

" 3단계: 프로덕션 수준 — 캐싱 + 테스트 가능 구조
CLASS zcl_payment_bank_reader DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    TYPES: BEGIN OF ty_summary,
             house_bank    TYPE hbkid,
             bank_name     TYPE string,
             account_count TYPE i,
           END OF ty_summary,
           tt_summary TYPE STANDARD TABLE OF ty_summary WITH KEY house_bank.

    METHODS get_summary
      IMPORTING iv_company_code   TYPE bukrs
      RETURNING VALUE(rt_summary) TYPE tt_summary
      RAISING   zcx_bank_read.

  PRIVATE SECTION.
    DATA mt_cache TYPE tt_summary.
    DATA mv_cached_bukrs TYPE bukrs.
ENDCLASS.

CLASS zcl_payment_bank_reader IMPLEMENTATION.
  METHOD get_summary.
    " 동일 회사 코드 반복 호출 대비 인스턴스 캐시
    IF mv_cached_bukrs = iv_company_code AND mt_cache IS NOT INITIAL.
      rt_summary = mt_cache.
      RETURN.
    ENDIF.

    SELECT FROM I_HouseBank AS hb
      LEFT OUTER JOIN I_HouseBankAccount AS acct
        ON  acct~CompanyCode = hb~CompanyCode
        AND acct~HouseBank   = hb~HouseBank
      FIELDS hb~HouseBank              AS house_bank,
             hb~\_Bank-BankName        AS bank_name,
             COUNT( acct~HouseBankAccount ) AS account_count
      WHERE hb~CompanyCode = @iv_company_code
      GROUP BY hb~HouseBank, hb~\_Bank-BankName
      ORDER BY hb~HouseBank
      INTO CORRESPONDING FIELDS OF TABLE @rt_summary.

    IF rt_summary IS INITIAL.
      RAISE EXCEPTION NEW zcx_bank_read(
        textid = zcx_bank_read=>no_house_bank ).
    ENDIF.

    mt_cache        = rt_summary.
    mv_cached_bukrs = iv_company_code.
  ENDMETHOD.
ENDCLASS.

단위 테스트에서는 ABAP 7.52 이상에서 제공되는 CL_CDS_TEST_ENVIRONMENT로 I_HouseBank를 테스트 더블로 대체해, 실제 커스터마이징 데이터 없이도 집계 로직을 검증할 수 있습니다. 집계(COUNT/GROUP BY)를 DB 계층에 내려보내는 코드 푸시다운 덕분에 계좌가 수천 건이어도 애플리케이션 서버로 전송되는 데이터는 요약 결과뿐입니다.

전환 시 체크포인트 — 흔히 하는 실수와 해결책

Q1. I_HouseBank를 SELECT했는데 은행명이 비어 있습니다.
I_HouseBank 자체에는 은행명 필드가 없습니다. 은행명은 은행 마스터(구 BNKA) 영역이므로 반드시 \_Bank-BankName 경로로 가져와야 합니다. 하우스 뱅크에 연결된 은행 키가 은행 마스터에 없으면(마이그레이션 누락 등) LEFT OUTER 시맨틱에 따라 NULL이 되니 데이터 정합성도 함께 점검하세요.

Q2. "\_Bank는 알 수 없는 이름입니다" 구문 오류가 납니다.
ABAP SQL에서 경로 표현식은 NetWeaver 7.50 이상에서 지원되며, 백슬래시 표기(\_Bank-BankName)와 대소문자를 정확히 맞춰야 합니다. 또한 별칭을 준 데이터 소스에서는 hb~\_Bank-BankName처럼 별칭을 앞에 붙여야 합니다.

Q3. 기존 프로그램의 BNKA 구조 기반 내부 테이블과 필드명이 안 맞습니다.
한 번에 전부 바꾸려 하지 말고, SELECT 문에서 AS 별칭으로 기존 필드명에 맞춰 매핑한 뒤 점진적으로 의미명 체계로 이관하는 전략이 현실적입니다. CORRESPONDING #( ... MAPPING ... )도 유용합니다.

Q4. 성능이 오히려 나빠진 것 같습니다.
LOOP 안에서 건별 SELECT하던 코드를 그대로 뷰만 바꾸면 이득이 없습니다. 뷰 전환과 동시에 FOR ALL ENTRIES·건별 조회를 집합 조회(JOIN/경로 표현식)로 재구성해야 HANA 최적화 효과를 얻습니다. 이런 재구성을 거치면 일반적으로 기존 대비 동등 이상의 성능을 기대할 수 있습니다.

이후에는 은행 마스터 자체를 다루는 I_Bank, 은행 계정 관리(BAM)의 I_BankAccountTP 계열, 그리고 RAP 기반 서비스에서 이 뷰들을 프로젝션으로 노출하는 패턴으로 학습을 확장하면 결제 영역 Clean Core 전환의 큰 그림이 잡힙니다. 더 살펴볼 만한 자료는 다음과 같습니다.

댓글 0

아직 댓글이 없습니다.