Зачастую при разработке возникает проблема формирования тестовых примеров. Эта статья поможет легко и быстро перекидывать данные из продуктивной системы в тестовую или в разработку и обратно, если между системами настроен RFC.
Принципы работы
Принцип работы очень простой. Соединение RFC позволяет вызвать ФМ, который находится в другой системе и получить от него результаты работы. Все что необходимо это придумать способ разбиения данных на пакеты(если данных слишком много, то можно словить дамп по нехватке памяти) и воспользоваться возможностями динамического программирования для унифицированного решения.
Реализация функционального модуля для вызова по RFC
Начнем с ФМ, полный код представлен ниже
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
FUNCTION Z92_COPY_TABLE. *"---------------------------------------------------------------------- *"*"Локальный интерфейс: *" IMPORTING *" VALUE(TAB_NAME) TYPE STRING *" VALUE(PACKAGE_NUMBER) TYPE I *" VALUE(PACK_SIZE) TYPE I *" VALUE(WHERE_STATEMENT) TYPE STRING OPTIONAL *" EXPORTING *" VALUE(JSON_RESULT) TYPE STRING *" VALUE(STATUS) TYPE STRING *"---------------------------------------------------------------------- data: tab_data type ref to data. field-symbols: <tab> type standard table. create data tab_data type standard table of (tab_name). assign tab_data->* to <tab>. data: count_records type i. select single count(*) from (tab_name) into count_records where (where_statement). if count_records > 0. data: number type i value 1. try . select * from (tab_name) package size pack_size into table <tab> where (where_statement) order by primary key. if sy-subrc = 0. if package_number = number. if count_records = pack_size * ( number - 1 ) + lines( <tab> ). status = |Скопировано { count_records } записей|. endif. exit. else. number = number + 1. endif. endif. endselect. json_result = /ui2/cl_json=>serialize( data = <tab> compress = abap_true pretty_name = /ui2/cl_json=>pretty_mode-camel_case ). catch cx_root. status = |Ошибка во время формирования набора данных|. endtry. else. status = |По запросу "{ where_statement }" нет записей|. endif. ENDFUNCTION. |
Функциональный модуль на вход получает
- Название таблицы из которой нужно взять данные — TAB_NAME
- Номер пакета (необходимо для больших сетов данных) — PACKAGE_NUMBER
- Количество забираемых записей в одном пакете — PACK_SIZE
- Условия выборки — WHERE_STATEMENT
Соответственно возвращает 2 параметра
- Результат в формате json — JSON_RESULT
- статус считывания — STATUS
Алгоритм работы следующий
- Динамически создается таблица
- Считывается константа — всего количество строк для выбранного условия (WHERE_STATEMENT).
- Считываются данные. Тут нужно отметить, что на считывание влияют параметры пакета и размера пакета. К сожалению ABAP не позволяется асинхронно возвращать данные. В любом случаи, например при обратном вызове RFC модуля, произойдет не явный коммит. По этому придется мериться с тем, что при очень больших сетах данных для поиска нужного пакета данных, нам придется перебрать все предыдущие пакеты т.е. по новой их считывать. Однако если Вам не нужно перекидывать сотни миллионов записей таким способом, то проблем с быстродействием возникнуть не должно. При переборе пакетов, нужный пакет вычисляется при помощи итератора (number), он должен совпадать с тем, что было передано в ФМ. Так как изначально нам никогда не известно сколько записей мы будем перекидывать между системами. Последний пакет мы будем высчитывать по формуле.
- Конвертируем внутреннюю таблицу в json строку, для этого используем стандартные методы.
Так же стоит отметить, что параметр STATUS должен быть пустым либо до тех пор пока не будет передан последний пакет, либо до поимки какой-то ошибки в качестве исключения.
Итак, мы написали ФМ для передачи данных в формате json. Но этот вариант не самый оптимальный. Так как мы передаем строку, то есть более экономный формат — CSV.
К сожалению стандартных способов конвертирования в CSV, когда писал этот алгоритм, я не нашел. По этому написал свой метод.
Вариант с CSV
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
FUNCTION Z92_COPY_TABLE_CSV. *"---------------------------------------------------------------------- *"*"Локальный интерфейс: *" IMPORTING *" VALUE(TAB_NAME) TYPE STRING *" VALUE(PACKAGE_NUMBER) TYPE I *" VALUE(PACK_SIZE) TYPE I *" VALUE(WHERE_STATEMENT) TYPE STRING OPTIONAL *" EXPORTING *" VALUE(CSV_RESULT_X) TYPE XSTRING *" VALUE(STATUS) TYPE STRING *"---------------------------------------------------------------------- data: tab_data type ref to data. field-symbols: <tab> type standard table. create data tab_data type standard table of (tab_name). assign tab_data->* to <tab>. data: count_records type i. select single count(*) from (tab_name) into count_records where (where_statement). if count_records > 0. data: number type i value 1. try . select * from (tab_name) package size pack_size into table <tab> where (where_statement) order by primary key. if sy-subrc = 0. if package_number = number. if count_records = pack_size * ( number - 1 ) + lines( <tab> ). status = |Скопировано { count_records } записей|. endif. exit. else. number = number + 1. endif. endif. endselect. data: csv_result type string. perform crteate_csv_string using <tab> changing csv_result. perform zip_scv using csv_result changing csv_result_x. catch cx_root. status = |Ошибка во время формирвоания набора данных|. endtry. else. status = |По запросу "{ where_statement }" нет записей|. endif. ENDFUNCTION. *&---------------------------------------------------------------------* *& Form CRTEATE_CSV_STRING *&---------------------------------------------------------------------* FORM CRTEATE_CSV_STRING using p_tab type standard table changing p_scv_string. data: lr_tabdescr type ref to cl_abap_structdescr, lr_data type ref to data, lt_dfies type ddfields. create data lr_data like line of p_tab. lr_tabdescr ?= cl_abap_structdescr=>describe_by_data_ref( lr_data ). lt_dfies = cl_salv_data_descr=>read_structdescr( lr_tabdescr ). data loc_line type string. loop at p_tab assigning field-symbol(<tes_line>). clear: loc_line. loop at lt_dfies assigning field-symbol(<ls_dfies>). data(valk) = |<tes_line>-{ <ls_dfies>-fieldname }|. assign (valk) to field-symbol(<df>). if sy-tabix = 1. loc_line = loc_line && <df>. else. loc_line = loc_line && ';' && <df>. endif. endloop. concatenate p_scv_string loc_line cl_abap_char_utilities=>newline into p_scv_string. endloop. ENDFORM. *&---------------------------------------------------------------------* *& Form ZIP_SCV *&---------------------------------------------------------------------* FORM ZIP_SCV using P_CSV_RESULT CHANGING P_CSV_RESULT_X. DATA: buffer_x TYPE xstring. call function 'SCMS_STRING_TO_XSTRING' exporting text = p_csv_result importing buffer = buffer_x exceptions failed = 1 others = 2. data: zip_tool type ref to cl_abap_zip. zip_tool = new #( ). zip_tool->add( exporting name = 'tdata.txt' content = buffer_x ). p_csv_result_x = zip_tool->save( ). ENDFORM. |
Данный вариант работает точно так же, только преобразование производится в CSV. И раз уж речь зашла об оптимизации размера передаваемых данных, в конце производится архивирование строки. Обратите внимание в этом варианте обратно передается не строка, а XSTRING, байтовые данные.
Вызов модуля
Далее нам нужно написать программу для вызова этих модулей.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
*&---------------------------------------------------------------------* *& Report Z92_LOAD_DATA *&---------------------------------------------------------------------* *& *&---------------------------------------------------------------------* REPORT Z92_LOAD_DATA. parameters: tab_name type string obligatory, packsize type i default 500000 obligatory, rfc type rfcdest obligatory, where type string. selection-screen begin of block block_5 with frame title text-b05. parameters: json radiobutton group tpp, csv radiobutton group tpp default 'X', del as checkbox. selection-screen end of block block_5. data: json_result type string, tab_data type ref to data, status type string. field-symbols: <tab> type any table. start-of-selection. data ok_code type i. perform ping_rfc changing rfc ok_code. check ok_code = 0. try . create data tab_data type standard table of (tab_name). assign tab_data->* to <tab>. catch cx_root. write: 'Таблица не найдена!'. exit. endtry. if del = abap_true. delete from (tab_name) where (where). if sy-subrc <> 0. write: 'Проблемы с удалением!'. exit. endif. commit work. endif. data: puck_number type i value 1. while status is initial. case abap_true. when json. perform json_format using tab_name puck_number where changing <tab> status. when csv. perform csv_format using tab_name puck_number where changing <tab> status. endcase. insert (tab_name) from table <tab>. commit work and wait. puck_number = puck_number + 1. endwhile. write: / 'Копирование завершено'. write: / tab_name. write: / where. write: / status. *&---------------------------------------------------------------------* *& Form PING_RFC *&---------------------------------------------------------------------* FORM PING_RFC CHANGING p_rfc type dest32 psubr type i. call function 'RFC_PING' destination p_rfc exceptions system_failure = 1 communication_failure = 2. if sy-subrc ne 0. call function 'RFC_PING' destination p_rfc exceptions system_failure = 1 communication_failure = 2. if sy-subrc ne 0. psubr = sy-subrc. if psubr = 1 . write 'system_failure'. elseif psubr = 2. write 'communication_failure'. else. write 'RFC_PING ERROR' . endif. endif. endif. ENDFORM. *&---------------------------------------------------------------------* *& Form JSON_FORMAT *&---------------------------------------------------------------------* FORM JSON_FORMAT using p_tabname p_puck_number p_where changing p_tab type standard table p_status. call function 'Z92_COPY_TABLE' destination rfc exporting tab_name = tab_name package_number = puck_number pack_size = packsize where_statement = where importing status = status json_result = json_result. /ui2/cl_json=>deserialize( exporting json = json_result pretty_name = /ui2/cl_json=>pretty_mode-camel_case changing data = <tab> ). ENDFORM. *&---------------------------------------------------------------------* *& Form CSV_FORMAT *&---------------------------------------------------------------------* FORM CSV_FORMAT using p_tabname p_puck_number p_where changing p_tab type standard table p_status. clear p_tab. data: csv_result type string, csv_result_x type xstring. call function 'Z92_COPY_TABLE_CSV' destination rfc exporting tab_name = p_tabname package_number = p_puck_number pack_size = packsize where_statement = p_where importing status = p_status csv_result_x = csv_result_x. perform unzip_data changing csv_result csv_result_x. split csv_result at cl_abap_char_utilities=>newline into table data(text_tab). loop at text_tab assigning field-symbol(<text_line>). split <text_line> at ';' into table data(values). insert initial line into table p_tab assigning field-symbol(<line>). loop at values assigning field-symbol(<value>). assign component sy-tabix of structure <line> to field-symbol(<component>). <component> = <value>. endloop. endloop. ENDFORM. *&---------------------------------------------------------------------* *& Form UNZIP_DATA *&---------------------------------------------------------------------* FORM UNZIP_DATA changing p_csv_result p_csv_result_x. data zip type ref to cl_abap_zip. zip = new #( ). zip->load( exporting zip = p_csv_result_x ). zip->get( exporting name = 'tdata.txt' importing content = p_csv_result_x ). data: xtab type table of x255, len type i. call function 'SCMS_XSTRING_TO_BINARY' exporting buffer = p_csv_result_x importing output_length = len tables binary_tab = xtab. call function 'SCMS_BINARY_TO_STRING' exporting input_length = len importing text_buffer = p_csv_result tables binary_tab = xtab exceptions failed = 1 others = 2. ENDFORM. |
Селекционный экран

Алгоритм работы простой. По данным с селекционного экрана запускается выбранный функциональный модуль. Модуль запускается по RFC в другой системе. Пришедшие данные преобразовываются и ложатся во внутреннюю таблицу, затем сохраняются в базу данных.
ФМ вызывается до тех пор, пока статус не вернет что-то, while status is initial.
В текущем решении я бы рекомендовал использовать максимальный размер одного пакета, который сможет выдержать Ваш сервер приложения. Так же, функцию «с удалением» я бы тоже рекомендовал использовать с осторожностью.
В заключении
На этом все. Таким простым способом Вы можете перебрасывать любые табличные данные. В конце хотел бы задеть вопрос производительности. По опыту могу сказать что основное время уходит на считывание данных. Время передачи, в моих системах, не значительное. Что касается объемов, я перебрасывал десятки миллионов записей за один запуск. Однако, тут алгоритм будет более эффективно работать, чем меньше будет общее количество пакетов. По этому лучше подбирать оптимальные условия для передачи в Ваших системах.