ABAP

CDS 개발자 90%가 놓치는 $session.user 자동 필터링 #shorts #SAP #ABAP

1. CDS 세션 변수가 필요한 이유

ABAP CDS에서 뷰를 작성하다 보면 "지금 접속한 사용자가 담당한 오더만 보여줘"라거나 "오늘 만료되는 계약만 조회해줘"와 같은 요구를 자주 받게 됩니다. 이때 화면에서 파라미터를 매번 넘겨받아 처리하면 개발자·사용자 모두 피곤합니다. CDS $session 컨텍스트 변수는 이런 상황에서 DB 레벨에서 자동으로 "현재 사용자"와 "오늘 날짜"를 채워주는 핵심 도구입니다.

하드코딩의 문제점은 명확합니다. WHERE responsible_person = 'DEVUSER01'처럼 사용자 ID를 박아두면 다른 사람에게 배포하는 순간 무용지물이 됩니다. 날짜도 마찬가지입니다. WHERE valid_to = '20260703'처럼 오늘 날짜를 하드코딩하면 매일 뷰를 수정해야 합니다. $session 변수는 이런 번거로움을 선언 한 줄로 해결합니다.

이 글에서는 CDS View Entity에서 $session.user$session.system_date를 활용한 동적 필터 작성, WHERE 절과 Association ON 조건에서의 활용 차이, Fiori Elements 앱에서 OData 요청에 세션 변수가 어떻게 반영되는지, 그리고 운영 환경에서의 성능·디버깅 체크리스트까지 다룹니다.

2. $session 컨텍스트 함수 개요 — system_date, user, language

CDS에서 $session은 "현재 실행 컨텍스트를 대변하는 가상 파라미터"라고 이해하면 편합니다. 뷰가 활성화되면 백엔드는 이를 SQL로 변환하고, HANA는 실행 시점에 세션 변수로 치환합니다. 즉, 뷰 텍스트에는 정적으로 $session.user라고 적혀 있지만 실제 실행되는 SQL에서는 로그인한 사용자의 값이 바인딩됩니다.

주요 $session 컨텍스트 함수입니다.

  • $session.user — 현재 ABAP 사용자 ID(대문자, 12자). sy-uname과 동일한 값
  • $session.system_date — 애플리케이션 서버의 현재 시스템 날짜(sy-datum)
  • $session.system_language — 현재 로그온 언어(1자, sy-langu)
  • $session.client — 현재 클라이언트(만델란트, sy-mandt)

비유하자면 $session은 "택배 송장에 자동으로 찍히는 발신자 정보"입니다. 사용자가 요청을 보낼 때마다 시스템이 알아서 스탬프를 찍어주기 때문에, 개발자는 "누가 조회했는지 몰라도 되고, 오늘 날짜를 물어볼 필요도 없이" 필터 조건을 선언적으로 쓸 수 있습니다.

주의할 점은 $session이 CDS View "안에서만" 특별한 의미를 가진다는 것입니다. Access Control(DCL)에서는 #user, #system_date처럼 다른 표기가 사용되며, 두 컨텍스트를 혼동하면 활성화 오류가 발생합니다.

3. $session.user로 담당 영업 오더만 필터링하기 (SalesOrder 시나리오)

영업 담당자(Sales Representative)가 자신의 오더만 조회하도록 하는 CDS View Entity 예제입니다.

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Sales Orders assigned to current user'
define view entity ZI_SalesOrderByRep
  as select from zsales_order_hdr as SalesHdr
{
  key SalesHdr.sales_order_id     as SalesOrderId,
      SalesHdr.customer_id        as CustomerId,
      SalesHdr.responsible_person as ResponsiblePerson,
      SalesHdr.order_date         as OrderDate,
      SalesHdr.gross_amount       as GrossAmount,
      SalesHdr.currency_code      as CurrencyCode
}
where
  SalesHdr.responsible_person = $session.user

Data Preview로 실행하면 로그인한 ABAP 사용자 ID(예: DEVUSER01)가 responsible_person과 일치하는 행만 노출됩니다. 앱에서 별도 파라미터를 넘길 필요가 없다는 것이 핵심 이점입니다.

이 방식을 사용할 때 가장 중요한 것은 대소문자 일관성입니다. $session.user는 항상 대문자 12자로 반환되므로 커스텀 테이블에 저장 시 반드시 대문자로 정규화해야 합니다. 소문자로 저장된 경우 필터가 작동하지 않고 빈 결과가 반환됩니다.

4. $session.system_date로 오늘 만료되는 계약 조회하기 (ContractApproval 시나리오)

계약 승인(Contract Approval) 시스템에서 "오늘 만료되는 계약"만 자동으로 걸러 대시보드에 표시하는 요구가 있다고 가정합니다. $session.system_date로 하드코딩 없이 처리합니다.

@EndUserText.label: 'Contracts expiring today'
define view entity ZI_ContractExpiringToday
  as select from zcontract_approval as Contract
  association [0..1] to ZI_BusinessPartner as _Partner
    on $projection.PartnerId = _Partner.PartnerId
{
  key Contract.contract_id      as ContractId,
      Contract.partner_id       as PartnerId,
      Contract.approval_status  as ApprovalStatus,
      Contract.valid_from       as ValidFrom,
      Contract.valid_to         as ValidTo,
      Contract.total_amount     as TotalAmount,
      dats_days_between($session.system_date, Contract.valid_to) as DaysRemaining,
      _Partner
}
where
      Contract.valid_to      =  $session.system_date
  and Contract.approval_status = 'A'

SELECT 절과 WHERE 절 양쪽에서 $session.system_date를 사용했습니다. 계산 컬럼 DaysRemaining은 항상 0이 되겠지만, 뷰를 재사용해 "3일 이내 만료" 같은 조건으로 확장할 때 유용합니다.

$session.system_date는 애플리케이션 서버의 시스템 날짜(sy-datum) 기준이므로, 서버가 UTC로 설정되어 있으면 한국 시간과 하루 차이가 날 수 있습니다. 사용자 로컬 시간대 기준의 "오늘"이 필요하다면 사용자 프로파일의 타임존을 조합해 커스텀 변환이 필요합니다.

5. WHERE 절 vs Association ON 조건에서의 활용 차이

세션 변수는 두 위치에서 뉘앙스가 달라집니다.

// WHERE 절: 결과 행 자체를 좁힘 — 헤더가 걸러짐
where SalesHdr.responsible_person = $session.user

// Association ON: 조인 결과의 부속 데이터만 좁힘 — 헤더는 남음
association [0..*] to zorder_item as _MyItems
  on  _MyItems.sales_order_id = $projection.SalesOrderId
  and _MyItems.last_editor    = $session.user

WHERE 절에서 사용하면 헤더 레코드 자체가 걸러지고, Association ON에서 사용하면 헤더는 남되 "내가 편집한 아이템"만 서브셋으로 붙습니다. 예를 들어 "전체 오더 중 내가 마지막으로 수정한 아이템 목록"이 필요하다면 ON 조건에 세션 변수를 배치합니다.

프로덕션에서 권장되는 패턴은 인터페이스 뷰(ZI_*)와 컨슈머 뷰(ZC_*)를 분리하고, 필터 조건을 Projection View에 두는 것입니다.

@AccessControl.authorizationCheck: #CHECK
@Metadata.allowExtensions: true
@EndUserText.label: 'My Approval Requests'
define root view entity ZC_ApprovalRequestMy
  provider contract transactional_query
  as projection on ZI_ApprovalRequest
{
  key RequestId,
      RequesterUser,
      Title,
      Priority,
      CreationDate,
      DueDate,
      Status,
      case
        when DueDate = $session.system_date then 'X'
        else ' '
      end as IsDueToday
}
where
      RequesterUser = $session.user
  and CreationDate >= $session.system_date - 30

#CHECK로 DCL을 함께 강제해 세션 변수 필터 우회를 방지하고, CASE 식 안에서 세션 변수를 활용해 오늘 마감 여부를 UI 배지로 표시할 수 있습니다.

6. Fiori 앱에서 세션 변수가 OData 요청에 반영되는 방식

Fiori Elements에서 위의 ZC_ApprovalRequestMy를 서비스로 노출하면, 사용자는 별도 필터를 걸지 않아도 "본인의 요청" 목록을 보게 됩니다. 내부 동작은 다음과 같습니다.

  1. 브라우저가 GET /sap/opu/odata4/.../ZC_ApprovalRequestMy 요청을 보냄
  2. ABAP 게이트웨이가 SQL로 변환하는 과정에서 $session.userSESSION_CONTEXT('APPLICATIONUSER')로 치환
  3. HANA가 실행 시점에 현재 접속 사용자 ID를 바인딩
  4. DB에서 필터링된 결과만 게이트웨이로 반환

중요한 점은 필터가 DB 레벨에서 적용되기 때문에 네트워크 페이로드를 낭비하지 않는다는 것입니다. 반면 클라이언트에서 $filter=RequesterUser eq 'DEVUSER01'을 강제로 넣으려 해도 세션 변수 필터가 우선 걸리므로 다른 사용자의 데이터를 조회하는 것이 원천적으로 차단됩니다(DCL과 조합 시).

7. 운영 환경 고려사항 — 성능, 인덱스, 테스트 우회, 디버깅

세션 변수 필터는 편리하지만, 프로덕션에서는 다음을 반드시 검토해야 합니다.

  • 인덱스 설계: responsible_person, requester_user처럼 자주 필터되는 컬럼은 secondary index 또는 partition key 후보로 검토합니다. 세션 변수 = 컬럼 조건은 파라미터 바인딩되므로 인덱스가 정상적으로 활용됩니다
  • 테스트 시 우회: 다른 사용자 데이터로 테스트가 필요하면 SU01에서 별도 테스트 계정을 만들거나, DCL에서 관리자 롤에 우회 규칙을 두는 방식을 권장합니다. 코드 안에서 $session.user를 조건부로 무시하도록 만드는 것은 안티패턴입니다
  • 디버깅: HANA Studio 또는 ADT의 SQL 콘솔에서 SELECT SESSION_CONTEXT('APPLICATIONUSER') FROM DUMMY로 현재 값 확인 가능합니다
  • 배치/RFC 컨텍스트: 백그라운드 잡이나 RFC 호출 시 $session.user는 잡을 실행하는 배치 사용자(예: WF-BATCH)가 됩니다. 원 요청자를 보존하려면 트랜잭션 데이터에 requester 컬럼을 명시적으로 저장해야 합니다
  • 성능 관찰: ST05, HANA PlanViz로 필터가 DB에 push down 되는지 확인합니다. CDS Table Function을 물려 쓰는 경우 push down이 깨질 수 있습니다

8. 자주 발생하는 오류와 해결법

$session.user로 필터했는데 결과가 항상 비어있습니다
가장 흔한 원인은 대소문자입니다. $session.user는 항상 대문자 12자로 반환되지만, 커스텀 테이블에 소문자로 저장했다면 매치되지 않습니다. 데이터 저장 시 to_upper()로 정규화하거나, 비교 시 대소문자를 맞춰 저장하는 것이 원칙입니다.

$session.system_date가 예상과 다른 날짜를 반환합니다
$session.system_date는 애플리케이션 서버의 시스템 날짜(sy-datum) 기준으로, 서버가 UTC로 설정되어 있으면 한국 시간과 하루 차이가 날 수 있습니다. 사용자 로컬 시간대 기준의 "오늘"이 필요하다면 tstmp_to_dats와 사용자 프로파일의 타임존을 조합해야 합니다.

뷰 활성화 시 "session variable not allowed here" 오류
Delivery Class(DC)가 C(Customizing)인 커스터마이징 뷰에서는 사용이 제한될 수 있습니다. 또한 define view 구식 문법에서 SELECT 절에 세션 변수를 직접 쓰면 문법 오류가 나기도 합니다. define view entity로 마이그레이션하는 것이 안전합니다.

DCL에서 $session.user 문법 오류
DCL(Access Control)에서는 $session.user가 아니라 #user, #system_date와 같은 aspect 문법을 사용해야 합니다. CDS View와 DCL의 표기법이 다르므로 혼동에 주의하세요.

Fiori Elements에서 필터가 무시되는 것 같습니다
/IWFND/CACHE_CLEANUP으로 게이트웨이 캐시를 지우고, 서비스 바인딩을 재활성화한 뒤 앱을 다시 로드해 확인하세요. Projection view에 붙어있는 원본 인터페이스 뷰에 필터가 없는 경우도 점검합니다.

댓글 0

아직 댓글이 없습니다.