SAP学习笔记 - 构建51 - RAP编写 Managed App Numbering 实战 Numbering- Travel ID,/DMO/TRV_M,debug,debug权限设定,异常处理
上一章讲了Locking、Numbering 的理论知识。
https://blog.csdn.net/shi_ly/article/details/149598604
本章继续学习RAP的知识。
- Numbering- Travel ID
- Number Range Object: /DMO/TRV_M
- debug,debug权限设定,异常处理
目录
1-1,Number Range Object: /DMO/TRV_M
1),Number Range Object: /DMO/TRV_M 在 RAP 开发中的作用
2),Number Range Object /DMO/TRV_M 的内部实现机制
1-2,cl_numberrange_runtime=>number_get
下面是详细内容。
1,Numbering - Travel ID
1-1,Number Range Object: /DMO/TRV_M
这个就是採番对象,调用它就可以自动给Travel ID 採番。
这个Number Range Object: /DMO/TRV_M 咱们深入看一下:
1),Number Range Object: /DMO/TRV_M 在 RAP 开发中的作用
在 SAP RAP (ABAP RESTful Application Programming) 开发中,Number Range Object /DMO/TRV_M
是一个编号范围对象,主要用于自动生成唯一标识符或编号。
a,主要用途
自动生成唯一ID:为业务对象(如旅行管理中的旅行记录)自动生成唯一编号
确保数据一致性:防止手动输入编号导致的重复或错误
遵循业务规则:按照预定义的格式生成编号
b,典型应用场景
在 RAP 开发中,特别是在旅行管理示例中(DMO代表Demo Travel Management),/DMO/TRV_M
通常用于:
自动生成旅行(Travel)记录的唯一ID
管理旅行编号的递增和格式
在创建新旅行实体时自动分配编号
c,技术实现
在 RAP 业务对象定义中,通常会这样使用:
abap
define behavior for /DMO/TRAVEL_M
...
{
// 使用编号范围
use number( number_range = '/DMO/TRV_M' );
...
}
这个编号范围对象需要在事务码SNRO中预先配置,定义编号的格式、起始值、当前值等属性。
d,相关概念
Number Range Object:SAP中管理自动编号的配置对象
RAP Number Handling:RAP框架中处理自动编号的机制
Business Object Initialization:创建业务对象时自动触发编号分配
在SAP标准旅行管理示例中,/DMO/TRV_M
是预定义的编号范围对象,专门用于旅行管理业务对象的编号生成。
那么它在内部是怎么实现的呢?
2),Number Range Object /DMO/TRV_M
的内部实现机制
在 SAP RAP 开发中,Number Range Object /DMO/TRV_M
的内部实现涉及多个层次的技术组件和交互流程。以下是其详细的内部工作原理:
a,底层数据库结构
编号范围对象的核心数据存储在以下表中:
SNRO - 编号范围对象主表(存储对象定义)
NRIV - 编号范围间隔表(存储各个间隔的定义)
NRIV_SAP - 系统预定义的编号范围间隔
对于 /DMO/TRV_M
,这些表会记录:
当前使用的编号
编号间隔(如 100000-999999)
是否外部给号
缓冲设置等
b,运行时处理流程
当 RAP 框架调用 use number( number_range = '/DMO/TRV_M' )
时:
b.1 初始化阶段
abap
" 伪代码表示内部实现
METHOD get_next_number.
CALL FUNCTION 'NUMBER_GET_NEXT'
EXPORTING
nr_range_nr = '01' " 间隔号
object = '/DMO/TRV_M' " 编号范围对象
IMPORTING
number = lv_number. " 获取的编号
ENDMETHOD.
b.2 锁机制
使用
ENQUEUE_ESNRO
函数进行锁定防止并发请求导致编号重复
采用缓冲机制减少数据库访问
c,RAP 框架集成
在行为定义中声明后,RAP 框架会自动处理:
abap
" RAP 运行时自动生成的代码(概念性)
METHOD create_travel.
" 1. 获取下一个编号
DATA(lv_travel_id) = cl_numberrange_runtime=>get_next(
iv_number_range = '/DMO/TRV_M'
).
" 2. 设置到实体
er_travel-TravelID = lv_travel_id.
" 3. 处理其他字段...
ENDMETHOD.
d,关键函数模块
内部调用的核心函数:
NUMBER_GET_NEXT
- 获取下一个编号NUMBER_CHECK
- 检查编号是否有效NUMBER_RANGE
- 维护编号范围
e,缓冲机制
为提高性能,SAP 采用多级缓冲:
本地内存缓冲(当前程序)
共享内存缓冲(应用服务器级别)
数据库持久化(最终存储)
缓冲大小通过 SNRO-BUFFERED
字段控制。
f,异常处理
内部实现的错误检查包括:
编号范围耗尽
并发访问冲突
无效间隔配置
权限检查
g,自定义扩展点
可通过 BAdI NUMBER_RANGE
实现自定义逻辑:
编号格式化
特殊校验规则
外部系统集成
h,与 CDS 的集成
在 CDS 视图定义中,对应的字段通常会标记为:
sql
@Semantics.quantity.unitCode: 'Currency'
TravelID : /dmo/travel_id;
这种内部实现确保了在 RAP 应用中编号生成的可靠性、高性能和一致性,同时保持了对传统 ABAP 编号范围机制的兼容性。
有了这个 Number Range Object: /DMO/TRV_M 之后,我们用它来採番。
1-2,cl_numberrange_runtime=>number_get
number_get函数:
这里介绍2个常用的快捷键:
打开代码:Ctrl+Shift+>
注释代码:Ctrl+Shift+ <
先写number_get代码:
- Exporting(输入参数)
- nr_range_nr:给号间隔,这里写了10,但其实它只认 01,故意写错,后面聊一下怎么排错
- object:/DMO/TRV_M,这就是咱们上面介绍的 给号对象,我感觉理解为存储过程比较好哈
- quantity = CONV #( lines( lt_entities ) )
这个东西不太好理解,因为咱们这里虽然是只需要一个号,但这个函数它是对应多个号的,
也就是说可以一次性给多个号,那你到底要几个啊,就是通过这个参数指定的
- IMPORTING(输出参数)
- number:当前最大的号(给出的号需要在当前最大的号基础上自己再计算)
- returncode:调用后返回的代码,万一出错了呢,可以根据它来看调用结果
- returned_quantity:调用后返回的给号数量,比如你要3个号,那就返回3;
要是没返回所需的数量,比如要3个,只返回2个,就说明出错了呗
METHOD earlynumbering_create.
DATA(lt_entities) = entities.
DELETE lt_entities WHERE TravelId IS NOT INITIAL.
TRY.
cl_numberrange_runtime=>number_get(
EXPORTING
* ignore_buffer =
nr_range_nr = '10'
object = '/DMO/TRV_M'
quantity = CONV #( lines( lt_entities ) )
* subobject =
* toyear =
IMPORTING
number = DATA(lv_latest_num)
returncode = DATA(lv_retcd)
returned_quantity = DATA(lv_retqty)
).
CATCH cx_nr_object_not_found.
CATCH cx_number_ranges.
ENDTRY.
ENDMETHOD.
调用完之后,号码有了,接下来,要将採番数据给 append (追加)到 mapped 这个变量中去
Alt+F2,然后点到 z04_dv_travel_m,可以看到它需要2个参数
最重要的就是这几行:
LOOP AT lt_entities INTO DATA(ls_entities).
lv_current_num = lv_current_num + 1. =》当前最大的号上 + 1
APPEND VALUE #( %cid = ls_entities-%cid =》把 ls_entities-%cid 赋值过来
TravelId = lv_current_num ) =》把新採番好的号赋值过来
TO mapped-z04_dv_travel_m. =》把号码给加到mapped 变量中去
ENDLOOP.
METHOD earlynumbering_create.
DATA(lt_entities) = entities.
DELETE lt_entities WHERE TravelId IS NOT INITIAL.
TRY.
cl_numberrange_runtime=>number_get(
EXPORTING
* ignore_buffer =
nr_range_nr = '10'
object = '/DMO/TRV_M'
quantity = CONV #( lines( lt_entities ) )
* subobject =
* toyear =
IMPORTING
number = DATA(lv_latest_num)
returncode = DATA(lv_retcd)
returned_quantity = DATA(lv_retqty)
).
CATCH cx_nr_object_not_found.
CATCH cx_number_ranges.
ENDTRY.
ASSERT lv_retqty = lines( lt_entities ).
* 写法上,这种稍微啰嗦一些,但是好理解一些
* DATA: ls_z04_dv_travel_m TYPE TABLE FOR MAPPED EARLY z04_dv_travel_m,
* lt_z04_dv_travel_m LIKE LINE OF ls_z04_dv_travel_m.
*
* DATA(lv_current_num) = lv_latest_num.
*
* LOOP AT lt_entities INTO DATA(ls_entities).
* lv_current_num = lv_current_num + 1.
*
* lt_z04_dv_travel_m = VALUE #( %cid = ls_entities-%cid
* TravelId = lv_current_num ) .
*
* APPEND lt_z04_dv_travel_m TO mapped-z04_dv_travel_m.
*
* ENDLOOP.
DATA(lv_current_num) = lv_latest_num.
LOOP AT lt_entities INTO DATA(ls_entities).
lv_current_num = lv_current_num + 1.
APPEND VALUE #( %cid = ls_entities-%cid
TravelId = lv_current_num )
TO mapped-z04_dv_travel_m.
ENDLOOP.
ENDMETHOD.
这样代码就写好了。
但是也不是就马上可以debug了,因为毕竟代码是放在远端的嘛(跟SAP ABAP代码一个思路),所以需要先授权。
1-3,debug 权限设定
右键Project > Properties
然后把自己用户给添加到User里面来
然后点Apply and close
然后就可以加断点进行debug了
1-4,debug
在前端点 开始按钮,
可以看到直接就到 get_instance_authorizations 里面去了,表示debug 设定OK
这样ListReport就可以表示出来了
然后点 登陆 按钮
输入数据之后,点 作成 按钮
这样就进入到 earlynumbering_create 函数中来了
出错了哈
往下走走,能看到这个出错框
界面正常也出错,这是 ASSERT lv_retqty = lines( lt_entities ). 这行代码出的
它判定说你没取到号啊,要1个号,给0个嘛,可不出错了
通信エラー<?xml version="1.0" encoding="utf-8"?><errorxmlns="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"><code>ASSERTION_FAILED</code><message>実行時エラー: 'ASSERTION_FAILED'。OData 要求処理が異常終了しました。Eclipse 向け ABAP 開発ツールのフィードリーダを使用するか、トランザクション /IWFND/ERROR_LOG、/IWBEP/ERROR_LOG、または ST22 を使用して、実行時エラーを分析してください。OData サーブすのアプリケーションコンポーネントで、SAP 提供のサービスで発生したエラーについてサポートチケットを作成してください。</message><timestamp>20250728072503</timestamp></error>
Eclipse上面,右下角那个小框框可能很快就消失了,那之后还能看吗?
可以的,就是通过 Feed Reader View来看
Windows > Other > Feed Reader
这里说是 RAISE_SHORTDUMP,由 CL_CSP_ACT_EVAL_NUMBERING_RESPCP 中引起
直接原因就是这行assert 出错
哎,其实看着还是挺晕乎的哈,估计还需要再进一步调查
上面那Message挺难看懂得,也不能把那个消息直接给客户看是吧。
这就需要做异常处理。
1-5,异常处理
异常处理函数:
CATCH cx_number_ranges INTO DATA(lo_error). ==》错误消息放到 lo_error,相当于Java 的ex
LOOP AT lt_entities INTO DATA(ls_entities).
APPEND VALUE #( %cid = ls_entities-%cid
%key = ls_entities-%key )
TO failed-z04_dv_travel_m. ==》给failed 赋值
APPEND VALUE #( %cid = ls_entities-%cid
%key = ls_entities-%key
%msg = lo_error )
TO reported-z04_dv_travel_m. ==》给reported 赋值
ENDLOOP.
EXIT. ==》一旦出错,则退出程序执行
ENDTRY.
CLASS lhc_Z04_DV_Travel_M DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
IMPORTING keys REQUEST requested_authorizations FOR Z04_DV_Travel_M RESULT result.
METHODS get_global_authorizations FOR GLOBAL AUTHORIZATION
IMPORTING REQUEST requested_authorizations FOR Z04_DV_Travel_M RESULT result.
METHODS earlynumbering_create FOR NUMBERING
IMPORTING entities FOR CREATE Z04_DV_Travel_M.
ENDCLASS.
CLASS lhc_Z04_DV_Travel_M IMPLEMENTATION.
METHOD get_instance_authorizations.
ENDMETHOD.
METHOD get_global_authorizations.
ENDMETHOD.
METHOD earlynumbering_create.
DATA(lt_entities) = entities.
DELETE lt_entities WHERE TravelId IS NOT INITIAL.
TRY.
cl_numberrange_runtime=>number_get(
EXPORTING
* ignore_buffer =
nr_range_nr = '10'
object = '/DMO/TRV_M'
quantity = CONV #( lines( lt_entities ) )
* subobject =
* toyear =
IMPORTING
number = DATA(lv_latest_num)
returncode = DATA(lv_retcd)
returned_quantity = DATA(lv_retqty)
).
CATCH cx_nr_object_not_found.
CATCH cx_number_ranges INTO DATA(lo_error).
LOOP AT lt_entities INTO DATA(ls_entities).
APPEND VALUE #( %cid = ls_entities-%cid
%key = ls_entities-%key )
TO failed-z04_dv_travel_m.
APPEND VALUE #( %cid = ls_entities-%cid
%key = ls_entities-%key
%msg = lo_error )
TO reported-z04_dv_travel_m.
ENDLOOP.
EXIT.
ENDTRY.
ASSERT lv_retqty = lines( lt_entities ).
* DATA: ls_z04_dv_travel_m TYPE TABLE FOR MAPPED EARLY z04_dv_travel_m,
* lt_z04_dv_travel_m LIKE LINE OF ls_z04_dv_travel_m.
*
* DATA(lv_current_num) = lv_latest_num.
*
* LOOP AT lt_entities INTO DATA(ls_entities).
* lv_current_num = lv_current_num + 1.
*
* lt_z04_dv_travel_m = VALUE #( %cid = ls_entities-%cid
* TravelId = lv_current_num ) .
*
* APPEND lt_z04_dv_travel_m TO mapped-z04_dv_travel_m.
*
* ENDLOOP.
DATA(lv_current_num) = lv_latest_num.
LOOP AT lt_entities INTO ls_entities.
lv_current_num = lv_current_num + 1.
APPEND VALUE #( %cid = ls_entities-%cid
TravelId = lv_current_num )
TO mapped-z04_dv_travel_m.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
这次的Message是不是稍微能好点儿了哈
オブジェクト /DMO/TRV_M に間隔 10/ は存在しません
診断
データベーステーブル NRIV は、出荷クラスが 'C' です。これは、SAP デフォルト設定がクライアント 000 にのみ存在することを意味しています。
手順
不足している番号範囲間隔をカスタマイジングで登録してください。
Msg 番号 NR751
中文翻译:
错误信息
对象 /DMO/TRV_M 中不存在间隔 10/诊断
数据库表 NRIV 的交付类别为 'C',这意味着 SAP 默认设置仅存在于客户端 000 中。解决方法
请通过定制配置注册缺失的编号范围间隔。消息编号 NR751
修改程序,再执行。
1-6,修改再执行
给号的Interval(间隔)设为01,然后再试试看
- nr_range_nr = '01'
Alt + F3激活,然后再试试看
Travel ID:25441
为啥是 25441 我也不知道,但是数据确实能登进去
再来1条,Travel ID 是 25442,应该是没啥问题的样子哈
以上就是本篇的全部内容。
更多SAP顾问业务知识请点击下面目录链接或东京老树根的博客主页