はじめに
この記事では、RAP(ABAP RESTful Application Programming Model)でキーの採番(Numbering)をする方法について紹介します。
シリーズの先頭はこちら☞ 【RAP】ADTのRAP Generatorを使ってみる
RAPでの採番方法
RAPでの採番方法には、以下のようなパターンがあります。
参考:Numbering
まず大きくEarly NumberingとLate Numberingがあり、Early Numberingはさらに枝分かれしています。これらの方法はBOのタイプ(Managed, Unmanaged)、ドラフトの有無にかかわらず使うことができます(※)。
Numberingの種類を説明するManaged、Unmagaedは、BOの種類とは関係なく、番号を自分で(Unmanaged)採番するか、フレームワークが(Managed)採番するかの違いを表しています。
※Late NumberingについてはUnmanagd BOでしか使えないという情報もあるので次回確認します。ドキュメントを見ると、どちらでも使えそうですが…
2022/1/15 更新
BTPのABAP環境では、Managed BOでもLate Numberingを使うことができました。
Early Numbering, Late Numberingとは
Early、Lateはキーの採番タイミングを指します。Early Numberingでは、エンティティの登録リクエストがあると即座に採番を行います。このあとでチェックやDBへの保存が行われるため、登録がキャンセルされた場合はその番号は不使用となり、番号に抜けが発生する可能性があります。Late NumberingではDBへの保存の直前に採番が行われ、この後にエラーは基本起こりえないため、番号に抜けが発生しません。
Early Numberingの分類
Early Numberingはさらに枝分かれします。External Early Numberingとは、キーをユーザの入力など外部から受け取ることを指します。これに対してInternal Early Numberingとは、自動で採番を行うことを指します。Managed Internal Early Numberingはフレームワークに番号の採番を任せる方法で、この場合キーに使えるのはUUIDのみです。Unmanaged Internal Early Numberingは自分で番号を採番する方法です。
Unmanaged Internal Early Numberingを実装
この記事では、Unmanaged Internal Early Numberingを実装してみます。この方法は、テーブルがUUIDでないキーを持ち、キーの採番を自動で行いたい場合に使用します。
ステップは以下のようになります。
- UUIDでないキーを持つテーブルを登録
- RAP GeneratorでBOを生成
- Early Numberingの実装
1. UUIDでないキーを持つテーブルを登録
以下のテーブルを定義します。
@EndUserText.label : 'Managed Person with Numeric Key'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zperson_num_m {
key client : abap.clnt not null;
key person_id : ze_person_id not null;
first_name : ze_firstname;
last_name : ze_lastname;
email : ze_email120;
birthday : ze_birthday;
status : ze_status;
created_by : abp_creation_user;
created_at : abp_creation_tstmpl;
last_changed_by : abp_locinst_lastchange_user;
local_last_changed_at : abp_locinst_lastchange_tstmpl;
last_changed_at : abp_lastchange_tstmpl;
}
2. RAP GeneratorでBOを生成
こちらの記事の要領でBOを生成します。
Behavior Definitionを見てみると、UUIDをキーにしていたときとは以下の違いがあります。
- キー項目がreadonlyになっていない
- numberingに関する指定がない
UUIDがキーのとき
{
field ( readonly )
PersonUUID,
CreatedAt,
CreatedBy,
LastChangedAt,
LocalLastChangedAt,
LastChangedBy;
field ( numbering : managed )
PersonUUID;
UUIDがキーでないとき
{
field ( readonly )
CreatedAt,
CreatedBy,
LastChangedAt,
LocalLastChangedAt,
LastChangedBy;
すなわちデフォルトでは、キーを外部から受け取るExternal Early Numberingになっているということです。
3. Early Numberingの実装
3.1. Behavior Definitionの設定
Behavior Definitionの先頭にearly numbering
の指定を追加します。また、キー項目であるPersonIDをreadonlyに設定します。
define behavior for ZI_PERSON_NUM_M alias Person
persistent table zperson_num_m
draft table zperson_num_m_d
etag master LocalLastChangedAt
lock master total etag LastChangedAt
authorization master( global )
early numbering //追加
{
field ( readonly )
CreatedAt,
CreatedBy,
LastChangedAt,
LocalLastChangedAt,
LastChangedBy,
PersonID; //追加
createのところに警告が出るので、Quick Fix(Ctrl + 1)でメソッドを実装します。
Behavior InplementationクラスのLocal Typeにメソッドearlynumbering_create
が追加されます。
CLASS LCL_HANDLER DEFINITION INHERITING FROM CL_ABAP_BEHAVIOR_HANDLER.
PRIVATE SECTION.
METHODS:
GET_GLOBAL_AUTHORIZATIONS FOR GLOBAL AUTHORIZATION
IMPORTING
REQUEST requested_authorizations FOR Person
RESULT result,
earlynumbering_create FOR NUMBERING
IMPORTING entities FOR CREATE Person.
ENDCLASS.
CLASS LCL_HANDLER IMPLEMENTATION.
METHOD GET_GLOBAL_AUTHORIZATIONS.
ENDMETHOD.
METHOD earlynumbering_create.
ENDMETHOD.
ENDCLASS.
3.2. Behavior Implementationの実装
メソッドearlynumbering_create
を以下のように実装します。メソッドのリターンパラメータはfailed
、reported
、mapped
という3つのテーブルです。failed、reportedはエラーの場合に返すもので、今回重要なのがmappedです。mappedにはフレームワークで採番される仮のキー(%cid)と自分で採番したキーのマッピングを返します。
デバッグで見ると、mapped-personは以下の構造でした。つまり、ドラフトかどうかもキーに含まれるということです。
最初の考え
最初は以下のような実装を考えました。簡単にするためDBから最大のキーを取得してそれをインクリメントします。
参考:BTPで番号範囲を使うには、以下の方法があります。
https://blogs.sap.com/2020/09/03/number-ranges-sap-cloud-platform/
METHOD earlynumbering_create.
"Get max person id from db
select max( person_id ) from zperson_num_m into @data(new_id).
loop at entities ASSIGNING FIELD-SYMBOL(<person>).
new_id += 1.
append value #(
%cid = <person>-%cid
%is_draft = <person>-%is_draft
personid = new_id
) to mapped-person.
endloop.
ENDMETHOD.
しかし、このメソッドはドラフトを登録したときと保存したときの2回動くことがわかりました。ドラフト状態のオブジェクトが複数作られるとキーがかぶってしまう可能性があります。そこで、以下のようにドラフトテーブルと本物のテーブルの両方を見に行くように変更しました。
また、保存時にドラフトと同じようにキーを採番すると、ドラフトのときのキーを上書きしてキーが変わってしまうこともわかったので、キーがすでに設定されている場合はその値をそのままmappedに渡すこととしました。
修正後
METHOD earlynumbering_create.
DATA new_id TYPE zperson_num_m-person_id.
LOOP AT entities ASSIGNING FIELD-SYMBOL(<person>).
if <person>-PersonID is not initial.
new_id = <person>-PersonID.
else.
new_id = get_newid( ).
endif.
APPEND VALUE #(
%cid = <person>-%cid
%is_draft = <person>-%is_draft
personid = new_id
) TO mapped-person.
ENDLOOP.
ENDMETHOD.
METHOD get_newid.
"Get max person id from db and draft
SELECT MAX( person_id ) FROM zperson_num_m INTO @DATA(max_id).
SELECT MAX( personid ) FROM zperson_num_m_d INTO @DATA(max_id_draft).
new_id = COND #( WHEN max_id > max_id_draft
THEN max_id
ELSE max_id_draft ).
new_id += 1.
ENDMETHOD.
4. 動作確認
"Create"を押して新規データを登録します。
ドラフトの状態でキーが採番されます。
保存せずに戻り、もう一つデータを登録します。正しくキーがインクリメントされました。
参考
How to use Early Numbering with Semantic Keys – RAP Managed BO