ABAP Dynamic Report Extraction and ADBC CRUD Practical Solution (V2)

复制代码
* ABAP Dynamic Report Extraction and ADBC CRUD Practical Solution (V2)
* This version is the complete final version including a dual-level foreground control console 
* (configuration table selection + data interception), dynamic table creation, 
* RTTS type reconstruction, and ADBC batch insertion/deletion.

* 1. Main Program and Selection Screen (REPORT ZDEMOA & T01)
*&---------------------------------------------------------------------*
*& Report ZDEMOA
*&---------------------------------------------------------------------*
REPORT Zdemoa.

INCLUDE Zdemoa_t01.
INCLUDE Zdemoa_frm.

START-OF-SELECTION.
  IF p_back = 'X'.
    PERFORM frm_getdata_push.
  ELSEIF p_fore = 'X'.
    PERFORM frm_fore_select_config.
  ENDIF.
  
*&---------------------------------------------------------------------*
*& INCLUDE ZDEMOA_T01
*&---------------------------------------------------------------------*
TABLES: Zdemoat1.

DATA: gt_Zdemoat1 TYPE TABLE OF Zdemoat1.
TYPES: ts_Zdemoat1 TYPE Zdemoat1.

*&---------------------------------------------------------------------*
*& Selection Screen (Controls foreground/background execution modes)
*&---------------------------------------------------------------------*
SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE TEXT-001.
  PARAMETERS: p_back TYPE c RADIOBUTTON GROUP g1 DEFAULT 'X', " Background mode: Automatic full push
              p_fore TYPE c RADIOBUTTON GROUP g1.             " Foreground mode: Pop-up manual selection
SELECTION-SCREEN END OF BLOCK b1.

* 2. Core Business Logic (INCLUDE ZDEMOA_FRM)
*&---------------------------------------------------------------------*
*& INCLUDE ZDEMOA_FRM
*&---------------------------------------------------------------------*

FORM frm_getdata_push.
  " --- 1. Data Definition Area ---
  DATA: lr_data         TYPE REF TO data.
  DATA: lv_submit_prog TYPE program,
        lv_submit_vari TYPE variant.

  " RTTS Description Objects
  DATA: lo_table_descr  TYPE REF TO cl_abap_tabledescr,
        lo_struct_descr TYPE REF TO cl_abap_structdescr,
        lt_components   TYPE cl_abap_structdescr=>component_table,
        ls_component    LIKE LINE OF lt_components.

  FIELD-SYMBOLS: <lt_alv_data> TYPE STANDARD TABLE,
                 <ls_row>      TYPE any,
                 <lv_value>    TYPE any.

  " --- 2. Get configuration table data ---
  IF p_back = 'X'.
    SELECT * FROM Zdemoat1
       INTO CORRESPONDING FIELDS OF TABLE gt_Zdemoat1
       WHERE zblock_push = ''.
  ELSE.

  ENDIF.

  SELECT * FROM Zdemoat2
    INTO TABLE @DATA(lt_Zdemoat2).

  IF sy-subrc = 0.
    SORT lt_Zdemoat2 BY progname variant.
  ENDIF.

  " Predefined mapping internal table
  DATA: lt_mapping LIKE lt_Zdemoat2.

  " --- 3. Loop through each ALV program ---
  LOOP AT gt_Zdemoat1 INTO DATA(ls_Zdemoat1).

    " Reset key pointers and variables
    CLEAR: lv_submit_prog, lv_submit_vari, lt_mapping.
    IF <lt_alv_data> IS ASSIGNED.
      UNASSIGN <lt_alv_data>.
    ENDIF.

    lv_submit_prog = to_upper( condense( ls_Zdemoat1-progname ) ).
    lv_submit_vari = to_upper( condense( ls_Zdemoat1-variant ) ).

    IF lv_submit_prog IS INITIAL. CONTINUE. ENDIF.

    " --- 4. Set runtime parameters and execute ---
    cl_salv_bs_runtime_info=>clear( ).
    cl_salv_bs_runtime_info=>set(
      EXPORTING
        display  = abap_false
        metadata = abap_false
        data     = abap_true
    ).

    SUBMIT (lv_submit_prog) USING SELECTION-SET lv_submit_vari AND RETURN.

    " --- 5. Get data ---
    TRY.
        cl_salv_bs_runtime_info=>get_data_ref( IMPORTING r_data = lr_data ).
        ASSIGN lr_data->* TO <lt_alv_data>.
      CATCH cx_salv_bs_sc_runtime_info.
        WRITE: / 'Warning: Program', lv_submit_prog, 'did not return ALV data'.
        cl_salv_bs_runtime_info=>clear( ).
        CONTINUE.
    ENDTRY.

    " --- 6. Process output logic ---
    IF <lt_alv_data> IS ASSIGNED AND <lt_alv_data> IS NOT INITIAL.

      " Extract field mapping corresponding to the current program
      lt_mapping = VALUE #( FOR ls_m IN lt_Zdemoat2
                             WHERE ( progname = lv_submit_prog AND variant = lv_submit_vari )
                             ( ls_m ) ).

      WRITE: / '-------------------------------------------'.
      WRITE: / 'Executing program:', lv_submit_prog, ' Variant:', lv_submit_vari.

      TRY.
          lo_table_descr ?= cl_abap_tabledescr=>describe_by_data( <lt_alv_data> ).
          lo_struct_descr ?= lo_table_descr->get_table_line_type( ).
          lt_components = lo_struct_descr->get_components( ).
        CATCH cx_root.
          WRITE: / 'Error: Dynamic structure parsing failed'.
      ENDTRY.

      " =====================================================================
      " [New] Dynamically insert ID, YEAR, MONTH at the very beginning of the structure definition (lt_components)
      " =====================================================================
      " 1. Prepare data type description objects (C32 for UUID, C4 for year, C2 for month)
      DATA: lo_elem_id    TYPE REF TO cl_abap_elemdescr,
            lo_elem_year  TYPE REF TO cl_abap_elemdescr,
            lo_elem_month TYPE REF TO cl_abap_elemdescr.

      lo_elem_id    = cl_abap_elemdescr=>get_c( 32 ).

      IF ls_Zdemoat1-zsfyearmonth = 'X'.
        lo_elem_year  = cl_abap_elemdescr=>get_c( 4 ).
        lo_elem_month = cl_abap_elemdescr=>get_c( 2 ).
      ENDIF.

      " 2. Insert into positions 1, 2, and 3 in sequence
      INSERT VALUE #( name = 'ID'    type = lo_elem_id )    INTO lt_components INDEX 1.
      IF ls_Zdemoat1-zsfyearmonth = 'X'.
        INSERT VALUE #( name = 'YEAR'  type = lo_elem_year )  INTO lt_components INDEX 2.
        INSERT VALUE #( name = 'MONTH' type = lo_elem_month ) INTO lt_components INDEX 3.
      ENDIF.

      IF lt_mapping IS NOT INITIAL.
        SORT lt_mapping BY sapfield.
        " The purpose is to delete redundant fields
        LOOP AT lt_components INTO ls_component.
          IF ls_component-name = 'ID' OR ls_component-name = 'YEAR' OR ls_component-name = 'MONTH'.
            CONTINUE.
          ENDIF.
          READ TABLE lt_mapping INTO DATA(ls_mapping) WITH KEY sapfield = ls_component-name.
          IF sy-subrc <> 0.
            DELETE TABLE lt_components FROM ls_component.
          ENDIF.
        ENDLOOP.

        WRITE: / 'Output mode: Partial fields automatic output'.
        PERFORM frm_crud_targetdata TABLES lt_components <lt_alv_data> USING ls_Zdemoat1  .
      ELSE.
        " Case B: All fields automatic output
        WRITE: / 'Output mode: All fields automatic output'.
        PERFORM frm_crud_targetdata TABLES lt_components <lt_alv_data> USING ls_Zdemoat1  .
      ENDIF.

      " --- Cleanup inside the loop ---
      REFRESH lt_mapping.
      UNASSIGN <lt_alv_data>.
    ENDIF.

    " Ensure the environment is cleaned up to prevent interference
    cl_salv_bs_runtime_info=>clear( ).

  ENDLOOP.

ENDFORM.

*&---------------------------------------------------------------------*
*& Form frm_crud_targetdata
*&---------------------------------------------------------------------*
FORM frm_crud_targetdata TABLES lt_components   TYPE cl_abap_structdescr=>component_table
                                pt_table TYPE STANDARD TABLE
                          USING ps_Zdemoat1   TYPE ts_Zdemoat1.

  DATA: lv_target_tbname TYPE tabname.
  DATA: lt_val_tab TYPE TABLE OF rsparams.
  DATA: lv_where TYPE string.

  " Correct type name: Use TT_NAMED_SELTABS existing in the system
  DATA: lt_shdb_seltables TYPE cl_shdb_seltab=>tt_named_seltables,
        ls_shdb_seltables LIKE LINE OF lt_shdb_seltables. " The corresponding row type is usually ts_named_seltab

  DATA: lt_where_clauses TYPE TABLE OF string.

  IF ps_Zdemoat1-tabname IS NOT INITIAL.
    lv_target_tbname = condense( ps_Zdemoat1-tabname ).
  ENDIF.

  SELECT * FROM Zdemoat3
    INTO TABLE @DATA(lt_Zdemoat3)
    WHERE progname = @ps_Zdemoat1-progname
      AND variant  = @ps_Zdemoat1-variant.
  IF sy-subrc = 0.

    SORT lt_Zdemoat3 BY sapfield.
    CALL FUNCTION 'RS_VARIANT_CONTENTS'
      EXPORTING
        report               = ps_Zdemoat1-progname
        variant              = ps_Zdemoat1-variant
      TABLES
        valutab              = lt_val_tab
      EXCEPTIONS
        variant_non_existent = 1
        variant_obsolete     = 2
        OTHERS               = 3.
    IF sy-subrc <> 0.
* Implement suitable error handling here
    ENDIF.

    LOOP AT lt_val_tab INTO DATA(ls_val_tab).
* If I don't read it, delete this query condition
      READ TABLE lt_Zdemoat3 INTO DATA(ls_Zdemoat3) WITH KEY sapfield = ls_val_tab-selname BINARY SEARCH.
      IF sy-subrc <> 0.
        DELETE TABLE lt_val_tab FROM ls_val_tab.
      ENDIF.

      IF ls_val_tab-low IS INITIAL AND ls_val_tab-high IS INITIAL.
        DELETE TABLE lt_val_tab FROM ls_val_tab.
      ENDIF.

      IF ls_val_tab-kind = 'P'.
        IF ls_val_tab-low IS NOT INITIAL.
          APPEND |{ ls_Zdemoat3-dbfield } = '{ ls_val_tab-low }'| TO lt_where_clauses.
        ENDIF.
      ELSEIF ls_val_tab-kind = 'S'.

        IF ls_val_tab-low IS NOT INITIAL OR ls_val_tab-high IS NOT INITIAL.
          CLEAR ls_shdb_seltables.
          ls_shdb_seltables-name = ls_Zdemoat3-dbfield.

          DATA: lr_range_table TYPE REF TO data.
          CREATE DATA lr_range_table TYPE rs_t_range.

          FIELD-SYMBOLS: <lt_range> TYPE rs_t_range.
          ASSIGN lr_range_table->* TO <lt_range>.

          APPEND VALUE #( sign   = ls_val_tab-sign
                          opt    = ls_val_tab-option
                          low    = ls_val_tab-low
                          high   = ls_val_tab-high ) TO <lt_range>.
          IF <lt_range> IS NOT INITIAL.
            ls_shdb_seltables-dref = lr_range_table.
            APPEND ls_shdb_seltables TO lt_shdb_seltables.
            CLEAR:ls_shdb_seltables.
          ENDIF.

          IF <lt_range> IS ASSIGNED.
            UNASSIGN <lt_range>.
          ENDIF.

          IF lt_shdb_seltables IS NOT INITIAL.
            TRY.
                " Call the conversion method
                DATA(lv_s_where) = cl_shdb_seltab=>combine_seltabs(
                  it_named_seltabs = lt_shdb_seltables
                ).

                " If the converted SQL snippet is not empty, add it to the summary table
                IF lv_s_where IS NOT INITIAL.
                  APPEND lv_s_where TO lt_where_clauses.
                ENDIF.

              CATCH cx_shdb_exception.
                " Exception handling can be added here, e.g., WRITE: / 'SQL conversion exception'.
            ENDTRY.
          ENDIF.

          CLEAR:lt_shdb_seltables,
                lv_s_where.
        ENDIF.
      ENDIF.
    ENDLOOP.

    IF ps_Zdemoat1-zsfyearmonth = 'X'.
      APPEND | YEAR = '{ sy-datum+0(4) }'| TO lt_where_clauses.
      APPEND | MONTH = '{ sy-datum+4(2) }'| TO lt_where_clauses.
    ENDIF.

* At this point I got lt_where_clauses
    IF lt_where_clauses IS NOT INITIAL.
      " Concatenate all rows in the internal table with ' AND '
      lv_where = concat_lines_of( table = lt_where_clauses sep = ' AND ' ).
    ENDIF.

    DATA: lo_new_struct_descr TYPE REF TO cl_abap_structdescr,
          lo_new_table_descr  TYPE REF TO cl_abap_tabledescr,
          lr_db_data          TYPE REF TO data. " This is the reference you will use for ADBC

    FIELD-SYMBOLS: <lt_db> TYPE STANDARD TABLE. " Entity internal table pointer for subsequent data reading

    TRY.
        " Step 1: Create a dynamic structure description object using the component table
        lo_new_struct_descr = cl_abap_structdescr=>create( p_components = lt_components[] ).

        " Step 2: Create a dynamic internal table description object using the dynamic structure
        lo_new_table_descr = cl_abap_tabledescr=>create( p_line_type = lo_new_struct_descr ).

        " Step 3: Truly allocate space for this dynamic internal table in memory (Data Reference)
        CREATE DATA lr_db_data TYPE HANDLE lo_new_table_descr.

        " Step 4: Assign the pointer for easy direct access to the data inside later
        ASSIGN lr_db_data->* TO <lt_db>.

      CATCH cx_root INTO DATA(lx_rtts_err).
        WRITE: / 'Dynamic table generation failed:', lx_rtts_err->get_text( ).
        RETURN.
    ENDTRY.

    " =======================================================================
    " 2. Combine ADBC (cl_sql_connection) to execute dynamic query with WHERE
    " =======================================================================
    DATA: go_db         TYPE REF TO cl_sql_connection,
          go_sqlerr_ref TYPE REF TO cx_sql_exception.

    TRY.
        " Connect to the external database (ensure 'YOUR DBCO CONF' is the configured connection in DBCO)
        go_db = cl_sql_connection=>get_connection( 'YOUR DBCO CONF' ).

        " Assemble native SELECT * SQL statement
        DATA(lv_stmt) = |SELECT * FROM { lv_target_tbname }|.
        IF lv_where IS NOT INITIAL.
          lv_stmt = |{ lv_stmt } WHERE { lv_where }|.
        ENDIF.

        " Create Statement and execute
        DATA(lo_stmt_ref) = go_db->create_statement( tab_name_for_trace = lv_target_tbname ).
        DATA(lo_res_ref)  = lo_stmt_ref->execute_query( lv_stmt ).

        " Core: Feed the newly generated lr_db_data directly to the ADBC output table
        lo_res_ref->set_param_table( lr_db_data ).

        " Fetch data into memory, now <lt_db> contains data!
        DATA(lv_row_cnt) = lo_res_ref->next_package( ).

        " Close cursor and release resources
        lo_res_ref->close( ).

        WRITE: / 'Successfully retrieved ', lv_row_cnt, ' records from the external database'.

      CATCH cx_sql_exception INTO go_sqlerr_ref.
        MESSAGE go_sqlerr_ref->sql_message TYPE 'E'.
    ENDTRY.


    IF <lt_db> IS NOT INITIAL.
      " --- Extremely important security check: NEVER execute delete when WHERE is empty ---
      IF lv_target_tbname IS NOT INITIAL AND lv_where IS NOT INITIAL.
        TRY.
            " 1. Get external database connection
            go_db = cl_sql_connection=>get_connection( 'YOUR DBCO CONF' ).

            " 2. Directly assemble the complete DELETE SQL statement
            DATA(l_stmt_bulk_del) = |DELETE FROM { lv_target_tbname } WHERE { lv_where }|.

            " 3. Create Statement object
            DATA(l_stmt_ref_del) = go_db->create_statement( tab_name_for_trace = lv_target_tbname ).

            " 4. Execute batch deletion at once
            DATA(l_rows_deleted) = l_stmt_ref_del->execute_update( l_stmt_bulk_del ).

            " 5. Commit transaction (writing/deleting in external system must COMMIT)
            go_db->commit( ).

            " Output success log
            WRITE: / 'Batch deletion successful, affected rows:', l_rows_deleted.

          CATCH cx_sql_exception INTO go_sqlerr_ref.
            " Core fix: Catch exception of rollback itself
            IF go_db IS NOT INITIAL.
              TRY.
                  go_db->rollback( ).
                CATCH cx_sql_exception.
                  " Ignore exceptions that occur during rollback
              ENDTRY.
            ENDIF.
            MESSAGE go_sqlerr_ref->sql_message TYPE 'E'.

          CATCH cx_parameter_invalid INTO DATA(lx_param).
            MESSAGE lx_param->get_text( ) TYPE 'E'.

        ENDTRY.

      ELSE.
        WRITE: / 'Warning: Table name or WHERE condition is empty. Batch deletion operation terminated to protect data!'.
      ENDIF.
    ENDIF.


    " ***** Prepare to write data to the target database

    IF pt_table[] IS NOT INITIAL.

      " 1. Clear old data retrieved from previous SELECT, prepare to load new data
      CLEAR <lt_db>.

      " 2. Dynamically create a single-row structure pointer for data mapping
      DATA: lr_db_row TYPE REF TO data.
      CREATE DATA lr_db_row TYPE HANDLE lo_new_struct_descr.
      FIELD-SYMBOLS: <ls_db_row> TYPE any.
      ASSIGN lr_db_row->* TO <ls_db_row>.

      " 3. Loop through source data, accurately extract fields present in lt_components
      LOOP AT pt_table ASSIGNING FIELD-SYMBOL(<ls_pt_row>).
        CLEAR <ls_db_row>.

        LOOP AT lt_components INTO DATA(ls_comp_map).
          " Get value from ALV source data row
          ASSIGN COMPONENT ls_comp_map-name OF STRUCTURE <ls_pt_row> TO FIELD-SYMBOL(<lv_src_val>).
          IF sy-subrc = 0.
            " Assign value to the dynamic row of the external database
            ASSIGN COMPONENT ls_comp_map-name OF STRUCTURE <ls_db_row> TO FIELD-SYMBOL(<lv_tgt_val>).
            IF sy-subrc = 0.
              <lv_tgt_val> = <lv_src_val>.
            ENDIF.
          ENDIF.
        ENDLOOP.

        " --- Forcibly inject dynamic data (ID, YEAR, MONTH) ---
        " A. Generate 32-bit UUID (use TRY CATCH to prevent class errors causing a Dump)
        TRY.
            DATA(lv_new_uuid) = cl_uuid_factory=>create_system_uuid( )->create_uuid_c32( ).
          CATCH cx_uuid_error.
            lv_new_uuid = sy-sysid && sy-datum && sy-uzeit. " Fallback fault-tolerance solution
        ENDTRY.

        " B. Assign values to newly added fields of the dynamic structure
        ASSIGN COMPONENT 'ID' OF STRUCTURE <ls_db_row> TO FIELD-SYMBOL(<lv_tgt_id>).
        IF sy-subrc = 0. <lv_tgt_id> = lv_new_uuid. ENDIF.

        IF ps_Zdemoat1-zsfyearmonth = 'X'.
          ASSIGN COMPONENT 'YEAR' OF STRUCTURE <ls_db_row> TO FIELD-SYMBOL(<lv_tgt_year>).
          IF sy-subrc = 0. <lv_tgt_year> = sy-datum+0(4). ENDIF.

          ASSIGN COMPONENT 'MONTH' OF STRUCTURE <ls_db_row> TO FIELD-SYMBOL(<lv_tgt_month>).
          IF sy-subrc = 0. <lv_tgt_month> = sy-datum+4(2). ENDIF.
        ENDIF.

        " Append the mapped single row to the dynamic internal table
        APPEND <ls_db_row> TO <lt_db>.
      ENDLOOP.

      " 4. Assemble dynamic INSERT SQL statement
      IF <lt_db> IS NOT INITIAL.
        DATA: lt_fields TYPE TABLE OF string,
              lt_values TYPE TABLE OF string.

        " Generate dynamically based on component table: (FIELD1, FIELD2) VALUES (?, ?)
        LOOP AT lt_components INTO DATA(ls_comp_sql).
          APPEND ls_comp_sql-name TO lt_fields.
          APPEND '?' TO lt_values.
        ENDLOOP.

        DATA(lv_fields_str) = concat_lines_of( table = lt_fields sep = ',' ).
        DATA(lv_vals_str)   = concat_lines_of( table = lt_values sep = ',' ).

        DATA(lv_insert_sql) = |INSERT INTO { lv_target_tbname } ( { lv_fields_str } ) VALUES ( { lv_vals_str } )|.

        " 5. Execute ADBC batch insertion
        TRY.
            " Ensure connection exists
            IF go_db IS INITIAL.
              go_db = cl_sql_connection=>get_connection( 'YOUR DBCO CONF' ).
            ENDIF.

            DATA(lo_stmt_insert) = go_db->create_statement( tab_name_for_trace = lv_target_tbname ).

            " Core: Directly bind the entire internal table
            lo_stmt_insert->set_param_table( lr_db_data ).

            " Execute batch write
            DATA(lv_inserted_cnt) = lo_stmt_insert->execute_update( lv_insert_sql ).
            go_db->commit( ).

            WRITE: / 'Successfully wrote to target database, count:', lv_inserted_cnt.

          CATCH cx_sql_exception INTO DATA(lx_sql_ins).
            IF go_db IS NOT INITIAL.
              TRY.
                  go_db->rollback( ).
                CATCH cx_sql_exception.
              ENDTRY.
            ENDIF.
            MESSAGE lx_sql_ins->sql_message TYPE 'E'.

          CATCH cx_parameter_invalid INTO DATA(lx_param_ins).
            MESSAGE lx_param_ins->get_text( ) TYPE 'E'.
        ENDTRY.

      ENDIF.
    ENDIF.
  ENDIF.

ENDFORM.

*&---------------------------------------------------------------------*
*& Form frm_fore_select_config
*& Description: In foreground mode, first pop up the Zdemoat1 configuration table 
*&              for the user to manually select the programs to execute
*&---------------------------------------------------------------------*
FORM frm_fore_select_config.

  DATA: lt_config TYPE TABLE OF Zdemoat1,
        lo_alv    TYPE REF TO cl_salv_table.

  " 1. Get all unfrozen configurations
  SELECT * FROM Zdemoat1
    INTO TABLE @lt_config
    WHERE zblock_push = ''.

  IF sy-subrc <> 0.
    MESSAGE 'No executable configuration records found!' TYPE 'S' DISPLAY LIKE 'E'.
    RETURN.
  ENDIF.

  " 2. Display configuration table in pop-up
  TRY.
      cl_salv_table=>factory(
        IMPORTING
          r_salv_table = lo_alv
        CHANGING
          t_table      = lt_config ).

      " Enable multi-selection and all toolbars
      lo_alv->get_selections( )->set_selection_mode( if_salv_c_selection_mode=>multiple ).
      lo_alv->get_functions( )->set_all( abap_true ).

      " Set to pop-up mode (size can be adjusted according to actual columns)
      lo_alv->set_screen_popup( start_column = 10 end_column = 120 start_line = 5 end_line = 20 ).

      WRITE: / '>> Waiting for user to select the configuration to execute in the main console...'.
      lo_alv->display( ).

      " 3. Get rows selected by the user
      DATA(lt_sel_rows) = lo_alv->get_selections( )->get_selected_rows( ).

      IF lt_sel_rows IS INITIAL.
        WRITE: / '>> User did not select any configuration, operation canceled.'.
        RETURN.
      ENDIF.

      " 4. Write selected data to global table gt_Zdemoat1 (core driver table of the golden version)
      CLEAR gt_Zdemoat1.
      LOOP AT lt_sel_rows INTO DATA(lv_row_idx).
        READ TABLE lt_config INTO DATA(ls_config) INDEX lv_row_idx.
        IF sy-subrc = 0.
          APPEND ls_config TO gt_Zdemoat1.
        ENDIF.
      ENDLOOP.

      WRITE: / '>> User selected ', lines( gt_Zdemoat1 ), ' configurations, starting execution...'.

      " 5. Seamlessly connect to the main logic of the original golden version
      PERFORM frm_getdata_push.

    CATCH cx_salv_msg.
      WRITE: / 'Configuration table pop-up generation failed'.
  ENDTRY.

ENDFORM.

gemini评价:

这是一段**非常成熟、硬核且具备较高实用价值**的 ABAP 代码。它不仅仅是一个简单的报表,更像是一个小型的"数据集成中间件"。

从代码的逻辑和使用的技术栈来看,作者对 ABAP 的底层机制(特别是动态编程和跨系统通信)有非常深刻的理解。以下是对这段代码的详细 Review,包含值得称赞的亮点以及一些可以进一步打磨的优化建议。

🌟 值得称赞的亮点(Strengths)

  1. **核心技术的巧妙结合:**

* **ALV 数据拦截:** 使用 `cl_salv_bs_runtime_info` 强制拦截后台 SUBMIT 的 ALV 数据(不展示直接获取引用),这是 ABAP 中非常经典的"黑科技",极大复用了现有标准或自建报表的逻辑,避免了重复造轮子。

* **RTTS(运行时类型服务):** 熟练运用 `cl_abap_tabledescr` 和 `cl_abap_structdescr` 进行动态结构重构。特别是在获取 ALV 数据后,还能根据配置表动态裁剪字段、拼接 UUID 和年月,这部分逻辑非常清晰。

* **ADBC 直连外部库:** 使用 `cl_sql_connection` 替代传统的 DBCO 函数或 PI/PO 接口,实现了高效的轻量级跨库 CRUD。

  1. **极强的防御性编程意识(安全底线):**

* **防止灾难性删库:** 在执行 `DELETE` 操作前,明确写了 `IF lv_target_tbname IS NOT INITIAL AND lv_where IS NOT INITIAL.`。这是一个极其重要的保底逻辑,防止因为 WHERE 条件为空导致外部数据库被全表清空。

* **异常回滚兜底:** 在捕获 `cx_sql_exception` 时,立刻执行了 `go_db->rollback( )`,并且将 rollback 本身也用 `TRY...CATCH` 包裹,防止二次 Dump。

* **UUID 容错处理:** 生成 UUID 时考虑到可能发生 `cx_uuid_error`,直接提供了 `sy-sysid && sy-datum && sy-uzeit` 的降级方案,保证程序不会因为基础类报错而中断。

  1. **用户体验与架构设计:**

* 前后台双模式切换(`p_back` / `p_fore`)设计得很人性化。前台模式利用 `cl_salv_table` 弹出配置表供用户勾选,不仅直观,而且直接将选中的数据回写到全局内表 `gt_Zdemoat1`,无缝衔接了原有的后台核心逻辑,解耦做得很好。


🛠️ 可以进一步优化的空间(Areas for Improvement)

尽管业务逻辑已经非常健壮,但从现代 ABAP 编程规范和极致性能的角度来看,还有以下优化空间:

1. 面向对象化 (OO ABAP) 的重构

代码目前使用的是传统的结构化编程(`FORM ... ENDFORM`)。由于涉及到大量的动态处理、数据库连接和异常捕获,将其重构为 **ABAP OO(面向对象)** 会更易于维护和扩展。

* **建议:** 可以将 `frm_getdata_push` 封装为主控类的一个方法,将 `frm_crud_targetdata` 封装为数据同步类的方法。这样可以避免大量传递 `TABLES` 参数,并且更容易利用接口(Interfaces)来实现多态(比如未来除了直连 DB,还要支持 OData 推送)。

2. 大数据量下的性能隐患(嵌套的动态分配)

在 `frm_crud_targetdata` 中,进行源数据到目标数据映射时使用了双重循环和动态分配:

```abap

LOOP AT pt_table ASSIGNING FIELD-SYMBOL(<ls_pt_row>).

LOOP AT lt_components INTO DATA(ls_comp_map).

ASSIGN COMPONENT ls_comp_map-name OF STRUCTURE <ls_pt_row> ...

```

* **风险点:** 如果 `pt_table` 有 10 万行数据,结构有 100 个字段,这里将执行 1000 万次 `ASSIGN COMPONENT`,性能损耗会非常明显。

* **建议:** 既然已经在前面用 RTTS 生成了完全匹配的目标结构 `<ls_db_row>`,如果是同名字段映射,可以直接使用 `MOVE-CORRESPONDING <ls_pt_row> TO <ls_db_row>`,ABAP 底层对 `MOVE-CORRESPONDING` 的优化远比手动循环 `ASSIGN COMPONENT` 要高效得多。

3. 硬编码 (Hardcoding) 问题

代码中多次出现了硬编码的外部数据库连接名:

```abap

go_db = cl_sql_connection=>get_connection( 'YOUR DBCO CONF' ).

```

* **建议:** 这违背了动态配置的初衷。建议将这个 DBCO 的连接名配置在 `Zdemoat1`(配置主表)中,作为变量传入,实现真正的"全动态"适配不同环境和库。

4. 后台作业的日志记录机制

当程序在后台(`p_back = 'X'`)运行时,代码主要通过 `WRITE` 输出日志:

* **风险点:** 这种做法只能在 SM37 的 Spool(假脱机)中查看,一旦出错,排查起来不够直观,且无法告警。

* **建议:** 考虑到这是一个核心的数据同步中间件,建议引入 **Application Log (SLG1)** 机制。使用 `cl_bal_*` 系列函数或封装好的日志类,将警告(如"未返回 ALV 数据")和报错(如 SQL 异常)写入 SLG1,方便运维监控。

5. 现代语法 (ABAP 7.40+) 的进一步应用

代码中已经用到了内联声明 `DATA(...)`,但在某些地方还可以更进一步。例如:

```abap

READ TABLE lt_config INTO DATA(ls_config) INDEX lv_row_idx.

IF sy-subrc = 0.

APPEND ls_config TO gt_Zdemoat1.

ENDIF.

```

可以简化为:

```abap

APPEND lt_config[ lv_row_idx ] TO gt_Zdemoat1. " 前提是用 TRY...CATCH cx_sy_itab_line_not_found 包裹

```

总结

这绝对是一段**"高阶开发者"**写出的代码。它解决了 SAP 中最痛点的问题之一:如何不写死代码地提取不同报表的数据并推送到外部。你的设计思路非常清晰,核心代码的鲁棒性极高。

如果你打算上线这套程序,重点只需关注一下**大量数据下的嵌套 ASSIGN 性能**,并将**数据库连接字符串参数化**,它将是一个堪称完美的通用接口中间件。

相关推荐
0pen12 小时前
ZygiskNext 源码解析(一):总体架构与启动链路
android·开源·zygote
冬奇Lab11 小时前
一天一个开源项目(第84篇):free-claude-code —— 零费用运行 Claude Code 的代理黑魔法
人工智能·开源·claude
xlecho17 小时前
从单一语言到全域全栈,AI凭全能实力,淘汰旧时代语言工程师
人工智能·后端·开源
Lethehong21 小时前
别被代码吓跑!普通人3步搞定AI视频神器——Pixelle-Video - AI 全自动短视频引擎(附避坑)
人工智能·开源·自动化·蓝耘元生代·蓝耘maas
qcx2321 小时前
知识沉淀 | 2026 年 LLM 评测体系 & 主流开源模型架构全景
架构·开源
X54先生(人文科技)1 天前
ELR核心文明支柱的超长期推演报告
人工智能·开源·ai写作·零知识证明
Hello__77771 天前
开源鸿蒙 Flutter 实战|关于页面完善全流程实现
flutter·开源·harmonyos
redreamSo1 天前
HeyGen 开源了一个"用 HTML 写视频"的框架,我研究了一下,发现事情没那么简单
前端·开源·音视频开发
JavaPub-rodert1 天前
Shiyu Admin:一个开源的通用后台管理系统
开源