ABAP에서 외부 DB 와 연결하는 가장 오래된 방법은 EXEC SQL ... ENDEXEC 블록의 Native SQL 입니다. 빠르고 단순하지만, 동적 쿼리·예외 처리·트랜잭션 관리 측면에서는 한계가 큽니다. SAP 7.4 이후에는 더 객체지향적인 대안이 표준 제공됩니다 — ADBC(ABAP Database Connectivity).
ADBC는 cl_sql_connection · cl_sql_statement · cl_sql_result_set 같은 표준 클래스를 사용합니다. DBCO 에 등록된 커넥션을 가져와 동적 SQL을 실행하고 결과를 받는 흐름. 특히 MSSQL 을 게이트웨이로 두고 OPENQUERY 를 통해 타 DB(Oracle·TIBERO 등) 와 데이터를 주고받는 패턴 에서 ADBC 가 진가를 발휘합니다.
이 글은 ADBC 의 개념·MSSQL OPENQUERY 활용·실전 코드 를 통합 정리한 메모입니다. 특히 DML 수행 전 SET XACT_ABORT ON 필수 설정 까지 함께 다룹니다(빠지면 INSERT/UPDATE/DELETE 가 통째로 막히는 함정).
핵심 원리
ADBC 와 Native SQL(EXEC SQL) 을 비교합니다.
| 항목 | Native SQL (EXEC SQL) | ADBC (cl_sql_*) |
|---|---|---|
| 스타일 | 선언적 (블록 안 정적 SQL) | 객체지향 (클래스 인스턴스) |
| 동적 SQL | 제한적 | string template 으로 자유롭게 조립 |
| 예외 처리 | SY-SUBRC 만 |
cx_sql_exception TRY/CATCH |
| 커넥션 관리 | CONNECT/DISCONNECT |
get_connection/close 메서드 |
| 대상 DB | SAP DB + DBCO 외부 DB | DBCO 외부 DB (Linked Server 활용) |
핵심 트레이드오프: 단순 SAP 조회·간단한 외부 DB 접근 은 Native SQL이 가볍고, 동적·복잡한 외부 DB 연동 은 ADBC가 안전합니다. 회사·시스템 환경에 따라 선택이 달라지는데, 외부 시스템 연동에서 EAI(예: XI/PI)를 거치지 않고 직접 가야 할 때 ADBC + MSSQL Linked Server 조합이 자주 쓰입니다.
MSSQL 을 게이트웨이로 쓰는 패턴
ADBC 가 진가를 발휘하는 시나리오 — MSSQL 을 중간 게이트웨이로 두고 OPENQUERY 를 사용해 그 너머의 타 DB(Oracle·TIBERO 등) 와 데이터를 주고받는 구조.
흐름:
- ABAP → DBCO 에 등록된 MSSQL 커넥션 잡기
- MSSQL 에 Linked Server(연결된 서버) 가 등록되어 있어야 함 (SSMS의
서버 개체 → 연결된 서버에서 확인) - ABAP에서 SQL 보낼 때
OPENQUERY(LinkedServerName, '쿼리')형식으로 작성 - MSSQL이 Linked Server 를 통해 실제 타 DB 에 쿼리를 위임 실행
이 패턴은 EAI(XI/PI) 인터페이스를 거치지 않고도 DB to DB 직접 연결 이 가능하다는 장점이 있습니다. 단, MSSQL에 Linked Server 설정·권한 등이 사전에 구성되어 있어야 합니다(BC 담당자 영역).
1단계 — DBCO 커넥션 + Linked Server 확인
ABAP 코드 작성 전 두 가지 사전 준비가 필요합니다.
| 위치 | 확인 항목 |
|---|---|
SAP 측 (DBCO) |
외부 MSSQL 커넥션 이름·접속 정보·인증 등록 (BC 담당자) |
| MSSQL 측 (SSMS) | 서버 개체 → 연결된 서버에서 Linked Server 이름 확인 (예: EXT_DB_LINK) |

OPENQUERY 작성 시 주의사항(이미지에서 강조된 내용):
- CHAR 값:
''값''처럼 작은따옴표 두 번씩 처리 (이스케이프) - NUMC 값: 따옴표 불필요
- SELECT * 금지: 컬럼명을 명시적으로 나열해야 UPDATE 가 정상 동작
- OPENQUERY 첫 인자: MSSQL 에 등록된 Linked Server 이름이어야 함
2단계 — ADBC 커넥션 획득 (cl_sql_connection)
DBCO 에 등록된 커넥션 이름으로 ADBC 인스턴스를 가져옵니다.
DATA: lo_connection TYPE REF TO cl_sql_connection,
lo_statement TYPE REF TO cl_sql_statement,
lv_con_name TYPE dbcon-con_name VALUE 'EXT_DB_LINK',
lv_mtype TYPE c LENGTH 1,
lv_mtext TYPE string.
" DBCO 커넥션 가져오기
TRY.
lo_connection = cl_sql_connection=>get_connection( lv_con_name ).
lv_mtype = 'S'.
lv_mtext = 'DBLINK MSSQL 연결 성공'.
CATCH cx_sql_exception INTO DATA(lx_sql).
lv_mtype = 'E'.
lv_mtext = lx_sql->get_text( ).
ENDTRY.
IF lv_mtype = 'E'.
MESSAGE lv_mtext TYPE 'S' DISPLAY LIKE 'E'.
RETURN.
ENDIF.
핵심 포인트:
cl_sql_connection=>get_connection( 'name' )— 정적 메서드, 인스턴스 반환cx_sql_exception으로 예외 처리 (연결 실패·인증 오류 등)dbcon-con_name타입으로 커넥션 이름 변수 선언
3단계 — (필수) SET XACT_ABORT ON — 중첩 트랜잭션 허용
OPENQUERY 를 통한 DML(INSERT/UPDATE/DELETE) 은 MSSQL 분산 트랜잭션을 사용합니다. 이때 MSSQL 세션이 중첩 트랜잭션을 허용하지 않으면 곧바로 에러가 떨어집니다. 그래서 DML 을 실행하기 전에 반드시 SET XACT_ABORT ON 을 한 번 실행해줘야 합니다.
이 옵션은 세션 단위 설정이므로, 같은 커넥션 인스턴스를 유지하는 동안에는 첫 번째 DML 직전에 한 번만 실행하면 됩니다.
DATA: lv_sql TYPE string,
l_row_cnt TYPE i.
" ★ 필수: DML 수행 전 중첩 트랜잭션 허용
CLEAR lv_sql.
lv_sql = 'SET XACT_ABORT ON'.
lo_statement = lo_connection->create_statement( ).
l_row_cnt = lo_statement->execute_update( lv_sql ).
lo_connection->commit( ).
| 옵션 미설정 시 증상 | 원인 |
|---|---|
| OPENQUERY INSERT/UPDATE/DELETE 즉시 실패 | Linked Server 를 거치는 DML 은 분산 트랜잭션 — 중첩 허용 안 되면 거부 |
| 에러: "원격 트랜잭션 시작 불가" | MSDTC 분산 트랜잭션 코디네이터와 충돌 |
| 일부 행만 반영되고 롤백되지 않음 | XACT_ABORT OFF 기본값 — 런타임 에러 시 트랜잭션 자동 롤백 안 됨 |
핵심 포인트:
- SELECT 만 한다면 불필요 — 이 옵션은 DML 전용. 조회만 한다면 생략해도 됨
- DML 전 한 번만 — 세션 단위라 같은 connection 인스턴스에서 한 번만 실행
- commit( ) 까지 호출 — 옵션 변경도 트랜잭션이므로 commit 으로 마무리해야 이후 DML 에 적용
4단계 — Statement 생성 + execute_update
획득한 커넥션에서 Statement 를 만들고 SQL 을 실행합니다.
DATA: lv_sql TYPE string.
" 동적 SQL 조립 (string template)
lv_sql = |INSERT OPENQUERY(EXT_DB_LINK,| &&
| 'SELECT OP_DATE, PLANT, LOC_CD, MATL_TYPE, MATL_CD,| &&
| PROD_NO, PROD_WGT, CREATE_TIME FROM EXT_SCHEMA.EXT_STOCK_TABLE')| &&
| VALUES ('{ ls_data-budat }', '{ ls_data-werks }', '{ ls_data-lgort }',| &&
| '{ ls_data-mtart }', '{ ls_data-matnr }', '{ ls_data-charg }',| &&
| { ls_data-clabs }, getdate())|.
" Statement 생성 + 실행
lo_statement = lo_connection->create_statement( ).
lo_statement->execute_update( lv_sql ).
lo_connection->commit( ).
IF sy-subrc <> 0.
MESSAGE 'DB Insert Error' TYPE 'E'.
ENDIF.
핵심 메서드:
| 메서드 | 역할 |
|---|---|
connection->create_statement( ) |
Statement 인스턴스 생성 |
statement->execute_update( sql ) |
INSERT/UPDATE/DELETE 실행 (행 수 반환) |
statement->execute_query( sql ) |
SELECT 실행 (cl_sql_result_set 반환) |
connection->commit( ) |
트랜잭션 커밋 (변경 반영) |
connection->close( ) |
커넥션 종료 (반드시 호출 — 누락 시 풀 소진) |
5단계 — OPENQUERY 구문 작성
MSSQL OPENQUERY 패턴 4가지(SELECT/INSERT/UPDATE/DELETE) 를 정리합니다.
-- SELECT
SELECT * FROM OPENQUERY(L_TESTDB, 'SELECT * FROM LinkedServerTestTable');
SELECT * FROM OPENQUERY(OracleSvr, 'SELECT name FROM joe.titles
WHERE name = ''NewTitle''');
-- INSERT (괄호 안 SELECT 의 컬럼 목록과 VALUES 가 매칭)
INSERT OPENQUERY(L_TESTDB, 'SELECT NO, NAME FROM LinkedServerTestTable')
VALUES (1, '홍길동');
-- UPDATE (괄호 안 SELECT 의 WHERE 로 대상 행 식별)
UPDATE OPENQUERY(L_TESTDB,
'SELECT NAME FROM LinkedServerTestTable WHERE NO = 1')
SET NAME = '임꺽정';
-- DELETE
DELETE OPENQUERY(L_TESTDB,
'SELECT NO FROM LinkedServerTestTable WHERE NO = 1');
핵심 규칙:
- 첫 인자: MSSQL Linked Server 이름 (DB 이름이 아님)
- 두 번째 인자: 작은따옴표로 감싼 SQL 문자열 (한 줄)
- CHAR 값 안 작은따옴표는 두 번씩 (
''홍길동''식으로 이스케이프) - SELECT * 금지 — 명시적 컬럼 나열 권장. UPDATE 시 SELECT * 면 동작이 안 됨
6단계 — 커밋 + 연결 종료
ABAP 표준과 마찬가지로 변경 후 명시적 커밋, 작업 끝나면 반드시 close.
TRY.
lo_statement = lo_connection->create_statement( ).
lo_statement->execute_update( lv_sql ).
lo_connection->commit( ).
" ★ 핵심: 사용 끝났으면 반드시 close 호출 — 누락 시 커넥션 풀 소진
lo_connection->close( ).
CATCH cx_sql_exception INTO DATA(lx_sql).
" 예외 발생해도 close 시도
IF lo_connection IS BOUND.
lo_connection->close( ).
ENDIF.
MESSAGE lx_sql->get_text( ) TYPE 'S' DISPLAY LIKE 'E'.
ENDTRY.
흔히 빠뜨리는 함정
SET XACT_ABORT ON 누락 → DML 즉시 실패 (★ 최빈도)
OPENQUERY 로 INSERT/UPDATE/DELETE 를 시도하는데 "원격 트랜잭션을 시작할 수 없습니다" 류의 에러가 떨어진다면 99% 이게 원인. DML 첫 호출 전에 반드시 한 번 SET XACT_ABORT ON 을 실행하고 커밋해 두세요. SELECT 만 쓴다면 불필요.
close( ) 누락 → 커넥션 풀 소진
가장 흔한 함정. ADBC 인스턴스는 SAP 시스템 차원에서 공용 풀로 관리됩니다. 닫지 않으면 다음 요청이 풀에서 빌 때까지 대기 또는 실패. 사용 끝나면 반드시 close — try/catch 안에서도 finally 패턴으로 보장.
SELECT * 후 UPDATE 미동작
OPENQUERY 의 SELECT 가 SELECT * 면 UPDATE 가 무시되는 경우 있음. 컬럼명을 명시적으로 나열 하는 게 정답.
CHAR 이스케이프 누락
CHAR 값 안의 작은따옴표는 두 번씩 써야 함(''홍길동''). string template 에서 한 번씩만 넣으면 MSSQL 구문 에러. 동적 조립할 때 자동 이스케이프 헬퍼 함수 권장.
SQL Injection 위험
string template 으로 SQL을 조립할 때 사용자 입력을 그대로 넣으면 SQL Injection 발생. 입력값은 반드시 검증·이스케이프 처리.
DBCO 와 Linked Server 양쪽 모두 설정 필요
ABAP 코드만 짜고 DBCO 또는 Linked Server 설정이 안 되어 있으면 동작 안 함. BC 담당자와 협의해 양쪽 다 확인.
트랜잭션 분리
ADBC 트랜잭션은 SAP LUW 와 분리. connection->commit( ) 누락 시 외부 DB 반영 안 됨. ABAP 표준 COMMIT WORK 는 ADBC 와 별개.
대용량 처리 시 LOOP 안에서 매번 commit
수만 건 INSERT 를 LOOP 안에서 매 행 commit 하면 매우 느림. 적당한 단위(예: 1000건) 로 묶어 commit 하거나, 외부 DB 측에 BULK INSERT 패턴 적용.
예외 처리 누락
ADBC 는 SY-SUBRC 가 아닌 cx_sql_exception 으로 에러 보고. TRY/CATCH 없이 사용하면 연결 실패 시 즉시 덤프.
전체 코드 — 복사용 통합본
위 단계를 하나의 ABAP 프로그램으로 합친 통합본입니다. SE38에 그대로 복사해서 활성화하면 동작하며, DBCO 커넥션 이름·Linked Server 이름·테이블 스키마는 실제 환경에 맞게 교체하세요.
REPORT zexample_adbc_mssql.
DATA: lo_connection TYPE REF TO cl_sql_connection,
lo_statement TYPE REF TO cl_sql_statement,
lv_con_name TYPE dbcon-con_name VALUE 'EXT_DB_NAME', " DBCO 등록 이름
lv_sql TYPE string,
l_row_cnt TYPE i,
lv_mtype TYPE c LENGTH 1,
lv_mtext TYPE string.
* 예시 데이터 구조 (실제 환경에 맞게 교체) ---------------------------
DATA: BEGIN OF ls_stock,
budat TYPE sy-datum,
werks TYPE werks_d,
lgort TYPE lgort_d,
mtart TYPE mtart,
matnr TYPE matnr,
charg TYPE charg_d,
clabs TYPE labst,
END OF ls_stock.
START-OF-SELECTION.
* 1) 커넥션 획득 — cl_sql_connection ---------------------------------
* ★ 핵심: DBCO에 등록된 커넥션 이름 사용 + cx_sql_exception 예외 처리
TRY.
lo_connection = cl_sql_connection=>get_connection( lv_con_name ).
lv_mtype = 'S'.
lv_mtext = 'DBLINK MSSQL 연결 성공'.
CATCH cx_sql_exception INTO DATA(lx_conn).
lv_mtype = 'E'.
lv_mtext = lx_conn->get_text( ).
ENDTRY.
IF lv_mtype = 'E'.
MESSAGE lv_mtext TYPE 'S' DISPLAY LIKE 'E'.
RETURN.
ENDIF.
* 2) ★ 필수: DML 수행 전 중첩 트랜잭션 허용 ---------------------------
* OPENQUERY DML 은 분산 트랜잭션 — XACT_ABORT ON 없으면 즉시 실패
* SELECT 만 한다면 이 블록은 생략 가능
TRY.
CLEAR lv_sql.
lv_sql = 'SET XACT_ABORT ON'.
lo_statement = lo_connection->create_statement( ).
l_row_cnt = lo_statement->execute_update( lv_sql ).
lo_connection->commit( ).
CATCH cx_sql_exception INTO DATA(lx_xact).
WRITE: / 'SET XACT_ABORT ON 실패:', lx_xact->get_text( ).
lo_connection->close( ).
RETURN.
ENDTRY.
* 3) INSERT — OPENQUERY 로 타 DB 에 데이터 삽입 -----------------------
* ★ 핵심: SELECT * 아닌 컬럼명 명시 / CHAR 값은 따옴표 두 번씩
ls_stock-budat = sy-datum.
ls_stock-werks = '1000'.
ls_stock-lgort = '1010'.
ls_stock-mtart = 'FERT'.
ls_stock-matnr = 'MAT0001'.
ls_stock-charg = 'BAT0001'.
ls_stock-clabs = 100.
TRY.
lv_sql = |INSERT OPENQUERY(EXT_DB_LINK,| &&
| 'SELECT OP_DATE, PLANT, LOC_CD, MATL_TYPE, MATL_CD,| &&
| PROD_NO, PROD_WGT, CREATE_TIME FROM EXT_SCHEMA.EXT_STOCK_TABLE')| &&
| VALUES ('{ ls_stock-budat }', '{ ls_stock-werks }',| &&
| '{ ls_stock-lgort }', '{ ls_stock-mtart }',| &&
| '{ ls_stock-matnr }', '{ ls_stock-charg }',| &&
| { ls_stock-clabs }, getdate())|.
lo_statement = lo_connection->create_statement( ).
lo_statement->execute_update( lv_sql ).
lo_connection->commit( ).
WRITE: / 'INSERT 성공'.
CATCH cx_sql_exception INTO DATA(lx_ins).
WRITE: / 'INSERT 실패:', lx_ins->get_text( ).
lo_connection->close( ).
RETURN.
ENDTRY.
* 4) UPDATE — IF 처리 결과 플래그 갱신 --------------------------------
TRY.
DATA(lv_if_seq) = '12345'.
lv_sql = |UPDATE OPENQUERY(EXT_DB_LINK,| &&
| 'SELECT IF_SEQ, ERP_FLAG, UPDATE_TIME| &&
| FROM EXT_SCHEMA.EXT_IF_TABLE WHERE IF_SEQ = { lv_if_seq }')| &&
| SET ERP_FLAG = ''Y'', UPDATE_TIME = getdate()|.
lo_statement = lo_connection->create_statement( ).
lo_statement->execute_update( lv_sql ).
lo_connection->commit( ).
WRITE: / 'UPDATE 성공'.
CATCH cx_sql_exception INTO DATA(lx_upd).
WRITE: / 'UPDATE 실패:', lx_upd->get_text( ).
ENDTRY.
* 5) ★ 핵심: 사용 끝났으면 반드시 close — 커넥션 풀 소진 방지 ---------
IF lo_connection IS BOUND.
lo_connection->close( ).
ENDIF.
* ※ 참고:
* - SELECT 는 execute_query( ) 사용 → cl_sql_result_set 반환
* - 대용량 INSERT 는 batch 단위(예: 1000건)로 commit
* - SQL Injection 방지 — 사용자 입력은 반드시 검증·이스케이프
* - SAP LUW 와 ADBC 트랜잭션은 분리 — commit( ) 누락 시 반영 X
요약
| 단계 | 처리 | 핵심 |
|---|---|---|
| 1 | 사전 준비 | DBCO 커넥션 + MSSQL Linked Server 양쪽 모두 설정 확인 |
| 2 | 커넥션 획득 | cl_sql_connection=>get_connection( name ) + TRY/CATCH |
| 3 | ★ XACT_ABORT ON | DML 전 필수 — SET XACT_ABORT ON 으로 중첩 트랜잭션 허용 |
| 4 | Statement 실행 | create_statement( ) + execute_update( ) |
| 5 | OPENQUERY 구문 | 컬럼 명시 + CHAR 따옴표 두 번 + Linked Server 이름 |
| 6 | 마무리 | commit( ) + close( ) 반드시 호출 (풀 소진 방지) |
ADBC 는 단순 외부 DB 조회를 넘어 MSSQL 게이트웨이 + Linked Server + OPENQUERY 라는 강력한 조합으로 진가를 발휘합니다. EAI(XI/PI) 없이도 DB to DB 직접 연결이 가능해 데이터 흐름이 단순해지고 처리 속도가 빠른 게 장점입니다. 다만 SAP 권한 체크 우회·트랜잭션 분리·커넥션 누수 위험이라는 부담이 있으므로, DML 전 SET XACT_ABORT ON 필수·SELECT * 금지·close 누락 금지·SQL Injection 방어 네 가지만은 반드시 챙기는 게 안전합니다.
Disclaimer — 이 포스트는 실무 정리 노트를 바탕으로 AI 보조로 정리되었습니다. cl_sql_connection·cl_sql_statement 같은 ADBC 클래스는 SAP NetWeaver 7.0 이상에서 사용 가능하며, MSSQL OPENQUERY 패턴은 MSSQL Linked Server 설정·권한이 사전에 구성되어 있어야 동작합니다. SAP 권한 체크가 우회되므로 SQL Injection 방어와 권한 검증 로직을 별도 구현해야 합니다. 운영 환경 적용 전 개발 시스템에서 보안·트랜잭션 동작을 충분히 검증하시기 바랍니다.