1. ALV 구현이 자꾸 실패하는 진짜 이유
ABAP 리포트 개발에서 가장 흔한 요구사항은 "내부 테이블을 화면에 그리드로 보여달라"입니다. 그런데 처음 ALV(ABAP List Viewer)를 접한 개발자 상당수가 REUSE_ALV_GRID_DISPLAY 같은 함수 모듈이나 CL_GUI_ALV_GRID 클래스부터 시작하다가, 필드 카탈로그 오류·화면(Dynpro) 미생성·이벤트 미동작이라는 3가지 실패를 연달아 겪습니다. 이 글은 그 실패 패턴 3가지를 재현하고, SAP NetWeaver 7.0 이상에서 일반적으로 권장되는 CL_SALV_TABLE로 같은 요구사항을 최소 코드로 해결하는 과정을 다룹니다.
이 글을 끝까지 읽으면 다음을 스스로 점검할 수 있습니다.
- 클래식 ALV(함수 모듈 / CL_GUI_ALV_GRID)와 SALV 모델의 구조적 차이를 설명할 수 있다
- 필드 카탈로그 없이 내부 테이블을 그리드로 출력하는 최소 코드를 작성할 수 있다
- 컬럼 텍스트, 표준 툴바, 이벤트(더블클릭)까지 SALV로 처리할 수 있다
CX_SALV_MSG등 예외 클래스를 올바르게 처리할 수 있다
2. 시작 전에 알아두면 좋은 것
ABAP 기본 문법(내부 테이블, SELECT문, 구조체)과 SE38/ADT에서 실행 가능한 리포트 프로그램을 만들어 본 경험이 있으면 충분합니다. 객체지향 ABAP은 몰라도 따라올 수 있도록 팩토리 메서드 호출 문법을 그때그때 설명하지만, TRY...CATCH 예외 처리의 기본 개념은 미리 알아두면 3단계 코드를 이해하기 수월합니다.
3. 환경과 버전 요구사항
CL_SALV_TABLE은 SAP NetWeaver 7.0(2005년 출시) 이후 모든 릴리스에 포함되어 있습니다. 즉 SAP ECC 6.0, S/4HANA 온프레미스(1909, 2021, 2023 등) 어디서든 동일하게 동작합니다. 다만 주의할 점이 하나 있습니다.
- ABAP Cloud / BTP ABAP Environment(Steampunk)에서는 사용 불가 — SALV를 포함한 SAP GUI 기반 UI 기술은 클라우드 릴리스 대상이 아닙니다. 클라우드에서는 RAP + Fiori Elements가 대안입니다.
- S/4HANA 온프레미스에서도 SAP GUI(또는 GUI for HTML) 접속 환경에서만 화면이 렌더링됩니다.
- 예제 실습에는 표준 테이블
EKKO(구매오더 헤더)를 사용하므로 별도 데이터 준비가 필요 없습니다. 개발 권한(S_DEVELOP)과 SE38 또는 Eclipse ADT만 있으면 됩니다.
4. 핵심 개념 — 클래식 ALV와 SALV 모델의 구조 차이
ALV 기술의 세대를 비유하자면 이렇습니다. WRITE 리스트가 손글씨라면, REUSE_ALV_GRID_DISPLAY 함수 모듈은 수동 타자기입니다. 출력은 되지만 필드 카탈로그라는 "활자판"을 개발자가 한 글자씩 직접 조립해야 합니다. CL_GUI_ALV_GRID는 전동 타자기쯤 됩니다. 강력하지만 커스텀 컨테이너, Dynpro 화면, PBO/PAI 모듈이라는 부속품을 전부 직접 배선해야 합니다. 반면 CL_SALV_TABLE은 워드프로세서입니다. "이 테이블을 보여줘"라고 선언하면 나머지(필드 카탈로그 생성, 화면 관리, 툴바)를 프레임워크가 대신 처리합니다.
구조적으로 SALV 모델은 다음 3가지가 클래식 방식과 다릅니다.
- 팩토리 패턴 —
CREATE OBJECT가 아니라 정적 메서드cl_salv_table=>factory( )가 인스턴스를 생성해 돌려줍니다. 내부 테이블의 타입 정보(RTTI)를 팩토리가 읽어 필드 카탈로그를 자동 생성하기 때문에, DDIC 구조를 쓰면 컬럼 제목·정렬·통화 참조까지 사전 정의에서 그대로 가져옵니다. - 기능별 서브 객체 분리 — 툴바는
CL_SALV_FUNCTIONS_LIST, 컬럼은CL_SALV_COLUMNS_TABLE, 이벤트는CL_SALV_EVENTS_TABLE이 담당합니다. 메인 객체에서get_functions( ),get_columns( )처럼 게터로 꺼내 설정하는 일관된 API 구조입니다. - 화면 자동 관리 — 전체 화면 모드로 쓰면 Dynpro, 컨테이너, GUI 상태를 만들 필요가 없습니다.
display( )한 줄이 화면 호출까지 처리합니다.
클래식: 내부 테이블 → 필드 카탈로그 수작업 → (화면+컨테이너 생성) → 함수/메서드 호출 → 이벤트용 콜백 FORM/핸들러 배선
SALV: 내부 테이블 → factory( ) → 옵션 설정(선택) → display( )
한 가지 트레이드오프는 알아둬야 합니다. SALV의 표준 팩토리 방식은 읽기 전용 표시가 기본입니다. 셀 편집이 핵심 요구라면 여전히 CL_GUI_ALV_GRID가 일반적인 선택이며, 최신 릴리스(S/4HANA 2020 이후 일부 SP)에서는 SALV에도 제한적 편집 API가 추가되는 흐름이 있으니 시스템 릴리스 노트를 확인하는 것이 좋습니다.
5. 실패 재현과 해결 — 3단계 실전 코드
실패 패턴 요약: ① 함수 모듈 방식에서 필드 카탈로그의 fieldname 오타·누락으로 짧은 덤프 또는 빈 그리드 발생, ② CL_GUI_ALV_GRID를 화면·컨테이너 없이 호출해 아무것도 표시되지 않음, ③ 그리드는 떴지만 툴바 버튼이 비어 있고 더블클릭이 무반응. 아래 3단계는 이 세 가지를 SALV로 순서대로 해소합니다.
[1단계] 최소 구현 — 구매오더 목록 출력
필드 카탈로그 수십 줄 대신, 실질 로직은 factory와 display 두 줄입니다.
REPORT zpo_salv_step1.
DATA lt_po_header TYPE STANDARD TABLE OF ekko.
SELECT ebeln, bukrs, bsart, lifnr, aedat
FROM ekko
INTO CORRESPONDING FIELDS OF TABLE @lt_po_header
UP TO 200 ROWS.
DATA lo_grid TYPE REF TO cl_salv_table.
TRY.
cl_salv_table=>factory(
IMPORTING r_salv_table = lo_grid
CHANGING t_table = lt_po_header ).
lo_grid->display( ).
CATCH cx_salv_msg INTO DATA(lx_salv).
MESSAGE lx_salv->get_text( ) TYPE 'E'.
ENDTRY.
포인트는 두 가지입니다. 첫째, t_table은 CHANGING 파라미터라서 지역 임시 테이블을 인라인으로 넘길 수 없고 참조 가능한 변수여야 합니다. 둘째, factory는 CX_SALV_MSG를 던지므로 TRY 블록이 필수입니다(생략하면 구문 오류). EKKO 구조를 그대로 썼기 때문에 컬럼 제목이 DDIC 데이터 엘리먼트에서 자동으로 채워집니다 — 실패 패턴 ①이 원천 차단되는 이유입니다.
[2단계] 실무 시나리오 — 툴바·컬럼 제어·예외 로깅
실무에서는 사용자 정의 구조를 쓰는 경우가 많고, 이때 컬럼 텍스트가 비어 나오는 문제와 툴바 버튼(정렬·필터·엑셀 다운로드)이 없는 문제를 함께 해결해야 합니다.
REPORT zpo_salv_step2.
TYPES: BEGIN OF ty_po_line,
po_number TYPE ekko-ebeln,
vendor TYPE ekko-lifnr,
doc_type TYPE ekko-bsart,
net_total TYPE ekko-rlwrt,
currency TYPE ekko-waers,
END OF ty_po_line.
DATA lt_po_list TYPE STANDARD TABLE OF ty_po_line.
SELECT ebeln AS po_number, lifnr AS vendor,
bsart AS doc_type, rlwrt AS net_total, waers AS currency
FROM ekko
INTO CORRESPONDING FIELDS OF TABLE @lt_po_list
UP TO 500 ROWS.
DATA lo_grid TYPE REF TO cl_salv_table.
TRY.
cl_salv_table=>factory(
IMPORTING r_salv_table = lo_grid
CHANGING t_table = lt_po_list ).
lo_grid->get_functions( )->set_all( abap_true ).
DATA(lo_columns) = lo_grid->get_columns( ).
lo_columns->set_optimize( abap_true ).
DATA(lo_col) = lo_columns->get_column( 'PO_NUMBER' ).
lo_col->set_short_text( '구매오더' ).
lo_col->set_medium_text( '구매오더 번호' ).
lo_col->set_long_text( '구매오더 문서번호' ).
DATA(lo_amount) = CAST cl_salv_column_table(
lo_columns->get_column( 'NET_TOTAL' ) ).
lo_amount->set_currency_column( 'CURRENCY' ).
lo_grid->display( ).
CATCH cx_salv_msg cx_salv_not_found INTO DATA(lx_error).
MESSAGE lx_error->get_text( ) TYPE 'S' DISPLAY LIKE 'E'.
ENDTRY.
주의할 세부 사항: get_column( )의 컬럼명은 대문자여야 하며, 존재하지 않는 이름을 넘기면 CX_SALV_NOT_FOUND가 발생합니다. 금액 컬럼은 set_currency_column으로 통화 필드를 연결해야 JPY(소수점 0자리) 같은 통화에서 금액이 100배로 표시되는 사고를 막을 수 있습니다.
[3단계] 프로덕션 수준 — 이벤트 핸들러·클래스 구조·권한 고려
더블클릭으로 ME23N(구매오더 조회)에 점프하는 요구사항을 로컬 클래스로 구현합니다. 이벤트 핸들러 등록 누락이 실패 패턴 ③의 본체입니다.
REPORT zpo_salv_step3.
CLASS lcl_po_report DEFINITION FINAL.
PUBLIC SECTION.
METHODS: run,
on_double_click FOR EVENT double_click
OF cl_salv_events_table
IMPORTING row column.
PRIVATE SECTION.
DATA: mt_po_list TYPE STANDARD TABLE OF ekko,
mo_grid TYPE REF TO cl_salv_table.
ENDCLASS.
CLASS lcl_po_report IMPLEMENTATION.
METHOD run.
SELECT ebeln, bukrs, bsart, lifnr, aedat
FROM ekko
INTO CORRESPONDING FIELDS OF TABLE @mt_po_list
UP TO 500 ROWS.
IF mt_po_list IS INITIAL.
MESSAGE '표시할 구매오더가 없습니다' TYPE 'S' DISPLAY LIKE 'W'.
RETURN.
ENDIF.
TRY.
cl_salv_table=>factory(
IMPORTING r_salv_table = mo_grid
CHANGING t_table = mt_po_list ).
mo_grid->get_functions( )->set_all( abap_true ).
mo_grid->get_columns( )->set_optimize( abap_true ).
mo_grid->get_display_settings( )->set_striped_pattern( abap_true ).
SET HANDLER on_double_click FOR mo_grid->get_event( ).
mo_grid->display( ).
CATCH cx_salv_msg INTO DATA(lx_salv).
MESSAGE lx_salv->get_text( ) TYPE 'E'.
ENDTRY.
ENDMETHOD.
METHOD on_double_click.
READ TABLE mt_po_list INDEX row INTO DATA(ls_po).
IF sy-subrc = 0.
AUTHORITY-CHECK OBJECT 'S_TCODE'
ID 'TCD' FIELD 'ME23N'.
IF sy-subrc <> 0.
MESSAGE 'ME23N 권한이 없습니다' TYPE 'S' DISPLAY LIKE 'E'.
RETURN.
ENDIF.
SET PARAMETER ID 'BES' FIELD ls_po-ebeln.
CALL TRANSACTION 'ME23N' WITH AUTHORITY-CHECK.
ENDIF.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
NEW lcl_po_report( )->run( ).
프로덕션 관점의 포인트 3가지입니다. 첫째, 성능 — SALV는 데이터를 화면 프레임워크로 복사해 렌더링하므로 수십만 건을 그대로 넘기면 메모리와 응답 시간이 급격히 나빠집니다. 둘째, 보안 — 이벤트에서 다른 트랜잭션을 호출할 때는 WITH AUTHORITY-CHECK를 붙여 권한 우회를 막습니다. 셋째, 테스트 용이성 — 데이터 조회를 별도 메서드(또는 조회 클래스)로 분리하면 ABAP Unit에서 화면 없이 로직만 검증할 수 있습니다.
6. 자주 만나는 오류와 해결책
Q1. factory 호출에서 "CX_SALV_MSG를 처리하지 않았다"는 구문 오류가 납니다.
factory와 다수의 SALV 메서드는 검사 가능(class-based) 예외를 선언하고 있어 TRY...CATCH가 문법적으로 강제됩니다. 최소한 CATCH cx_salv_msg 블록을 감싸고, 컬럼 API를 쓴다면 cx_salv_not_found도 함께 잡는 것이 안전합니다.
Q2. 그리드는 뜨는데 정렬/필터/엑셀 버튼이 하나도 없습니다.
SALV 기본값은 툴바 기능 비활성입니다. get_functions( )->set_all( abap_true )를 display( ) 이전에 호출해야 합니다.
Q3. 사용자 정의 구조를 썼더니 컬럼 제목이 필드명 그대로(PO_NUMBER 등) 나옵니다.
로컬 TYPES 구조는 DDIC 텍스트가 없기 때문입니다. 해결책은 두 가지 — 구조 필드를 데이터 엘리먼트 타입으로 선언하거나, set_short_text / set_medium_text / set_long_text를 직접 지정합니다.
Q4. 더블클릭 이벤트가 전혀 반응하지 않습니다.
SET HANDLER ... FOR lo_grid->get_event( ) 등록이 빠졌거나, display( ) 이후에 등록한 경우입니다. 반드시 display( ) 호출 전에 핸들러를 연결하세요.
7. 실무 적용 시 주의사항 및 확장 포인트
이 글에서 다룬 것은 SALV 표시 모델의 최소 골격입니다. 이어서 살펴볼 만한 주제는 다음과 같습니다. 셀 단위 색상과 아이콘을 넣는 CL_SALV_FUNCTIONAL_SETTINGS와 컬러 컬럼, 계층 표시가 필요할 때의 CL_SALV_HIERSEQ_TABLE과 트리용 CL_SALV_TREE, 팝업 형태 표시를 위한 set_screen_popup, 그리고 편집 그리드가 필요한 경우의 CL_GUI_ALV_GRID 비교 학습입니다. 클라우드 전환을 준비한다면 SALV의 개념(메타데이터 기반 자동 UI)이 RAP + Fiori Elements의 List Report로 어떻게 계승되는지 살펴보는 것도 좋은 다음 여정입니다.
8. 더 읽어볼 자료
- SAP Help Portal — ALV Object Model 개요 (NetWeaver 7.5)
- SAP Help Portal — SALV Fullscreen Table 표시
- SAP Help Portal — SALV 이벤트 처리
- SAP Help Portal — ABAP Development Tools(ADT) 사용자 가이드
- SAP Community — SALV Table Display 관련 블로그 아카이브
댓글 0
아직 댓글이 없습니다.