如何回滚请求<复制系统初始的数据>所处理的数据

本文详细描述了在Oracle财务系统升级过程中遇到的数据重复问题,包括问题提出、解决过程、数据修复方法和相关补丁信息。通过分析复制系统初始数据请求的工作流程,作者提出了一种有效的方法来识别和回滚重复数据,并提供了具体的SQL查询和脚本来验证和清理受影响的表。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、    问题提出
请求名称:复制系统初始的数据
参数:
问题:
今天早上财务实施人员新配置了一个OU,然后在跑复制系统初始的数据报表的时候,不小心,不输入参数就直接跑。
报表先是报错。


接下来的问题更加严重了。因为这个请求会做很多塞数据的动作,导致一些设定表格数据重复了。
就是说,原来的业务实体已经存在设定的数据,因为跑这个报表的时候,没有限制业务实体就运行,所以,所有的业务实体的数据都会重新塞一次到相关的表格。
特别是表格:AR_SYSTEM_PARAMETERS_ALL,当OU数据重复的时候,标准的销售订单,应收应付参数设置等的地方,打开都会报一个异常的错误。


二、    解决问题的处理过程
核心问题点:如何回滚?

这里提出一个有效的解决办法。
1.       首先,必须要了解[复制系统初始的数据]这个请求究竟是做一个什么样的动作,就是要了解它的代码。

里面主要是调用ad_morg.replicate_seed_data过程来处理数据。
而过程ad_morg.replicate_seed_data主要是将一堆表格的数据,从默认的OU(-3113或者 - 3114 )复制数据到目标的业务实体(如果没有输入目标的业务实体,则所有的业务实体都会塞)。

所以,只要确定2个问题点:
它会将什么表格里面的什么数据,塞到新的业务实体里面。就可以做一个完整的回滚动作。

2.       确定该报表自动运行的SQL语句。
我这里用一个办法,将上面的过程所自动执行的Insert的SQL语句塞到一个表格里面。然后就可以慢慢分析了。
注意:下面的步骤必须到最新的测试环境做。
1)         新建临时表格。
create table XYG.xyg_ad_morg_temp(seq number,statement CLOB,parmeter varchar2(4000));
CREATE SYNONYM APPS.xyg_ad_morg_temp FOR XYG.xyg_ad_morg_temp;
2)         新建一个包xyg_ad_morg。
包xyg_ad_morg和ad_morg一模一样的。注意必须要取消自动执行的动态SQL语句还有COMMIT的语句。
下面是代码:

主要是增加:

还有修改:

等等。

3)         接着,执行下面的代码,自动产生数据到临时表。
declare
  X_org_id number;
begin
  xyg_ad_morg.g_table_seq := 0;
  dbms_output.enable(1000000);
  X_org_id := null;--to_number('&1');
  xyg_ad_morg.replicate_seed_data(X_org_id,NULL,NULL);

exception
  when others then
    raise;
end;

跑完之后,查询:
SELECT * FROM xyg_ad_morg_temp ORDER BY SEQ;



3.       接下来的是重点了,自己编写一道程序,自动验证究竟有多少数据会重复塞到现有的OU里面:
DECLARE
    L_DEBUG_MODE BOOLEAN := TRUE;
    L_SELECT_STATEMENT varchar2(32767);
    L_DEAL_TABLE   VARCHAR2(240);
    ---
    l_sum_row      number;
    l_sql          VARCHAR(32767);
    l_cursor       NUMBER;
    l_stat         NUMBER;
    ---
    CURSOR CUR_AD_MORG IS
    SELECT * FROM xyg_ad_morg_temp ORDER BY SEQ;
BEGIN
   dbms_output.enable(1000000);
    FOR REC_AD_MORG IN CUR_AD_MORG LOOP
        l_sum_row := 0;
        ---抓出要处理的表格
        L_DEAL_TABLE := SUBSTR(REC_AD_MORG.STATEMENT
                              ,INSTR(UPPER(REC_AD_MORG.STATEMENT),' ',1,2)+1
                              ,INSTR(UPPER(REC_AD_MORG.STATEMENT),' ',1,3)-INSTR(UPPER(REC_AD_MORG.STATEMENT),' ',1,2)-1);
        ----组合 SQL语句:
        L_SELECT_STATEMENT := 'SELECT COUNT(*) '||SUBSTR(REC_AD_MORG.STATEMENT,INSTR(UPPER(REC_AD_MORG.STATEMENT),' FROM ',1));
        /*
        IF L_DEBUG_MODE THEN
           DBMS_OUTPUT.PUT_LINE(SUBSTRB(L_SELECT_STATEMENT,1,240));
        END IF;
        */
        l_sql := L_SELECT_STATEMENT;
        
       --dbms_output.put_line(l_sql);
       --打开游标;
       l_cursor := dbms_sql.open_cursor;
       --解析动态 SQL语句; dbms_sql.native
       dbms_sql.parse(l_cursor, l_sql, 1);
      
       --绑定变量
       IF REC_AD_MORG.PARMETER LIKE '%-3113%' THEN
          dbms_sql.bind_variable(l_cursor, ':X_source_org_id', -3113);
       ELSE
          dbms_sql.bind_variable(l_cursor, ':X_target_org_id', '');
          dbms_sql.bind_variable(l_cursor, ':X_source_org_id', -3114);
       END IF;
      
        --定义列
       dbms_sql.define_column(l_cursor, 1, l_sum_row);
       --执行动态 SQL语句。
       l_stat := dbms_sql.execute(l_cursor);                  

       LOOP
         --fetch_rows在结果集中移动游标,如果未抵达末尾,返回 1
          EXIT WHEN dbms_sql.fetch_rows(l_cursor) <= 0;
          --将当前行的查询结果写入上面定义的列中。
          dbms_sql.column_value(l_cursor, 1, l_sum_row);
       END LOOP;
      
       --关闭游标。
       dbms_sql.close_cursor(l_cursor);
      
       ---输出我们想要的结果:
       IF l_sum_row > 0 THEN
        dbms_output.put_line('SEQ:'||REC_AD_MORG.SEQ||' 表格 :'||L_DEAL_TABLE||' 自动塞记录行数 :'||l_sum_row);
       END IF;
    END LOOP;
END;

4.      下面的是日志记录:

SEQ:1 表格:AP_1096_DATA_ALL 自动塞记录行数:40

SEQ:5 表格:AP_TOLERANCES_ALL 自动塞记录行数:40

SEQ:17 表格:AR_SYSTEM_PARAMETERS_ALL 自动塞记录行数:40

SEQ:49 表格:CN_OBJECTS_ALL 自动塞记录行数:3

SEQ:51 表格:CN_PERIOD_TYPES_ALL 自动塞记录行数:120

SEQ:73 表格:CN_TABLE_MAP_OBJECTS_ALL 自动塞记录行数:560

SEQ:107 表格:OE_PAYMENT_TYPES_ALL 自动塞记录行数:240

SEQ:109 表格:OE_PAYMENT_TYPES_TL 自动塞记录行数:480

SEQ:137 表格:OZF_CLAIM_DEF_RULES_ALL 自动塞记录行数:560

SEQ:141 表格:PO_CHANGE_ORDER_TOLERANCES_ALL 自动塞记录行数:2680

SEQ:143 表格:CN_CW_WORKBENCH_ITEMS_ALL_B 自动塞记录行数:320

SEQ:145 表格:CN_CW_WORKBENCH_ITEMS_ALL_TL 自动塞记录行数:640

SEQ:147 表格:CN_CW_SETUP_TASKS_ALL_B 自动塞记录行数:600

SEQ:149 表格:CN_CW_SETUP_TASKS_ALL_TL 自动塞记录行数:1200

       还好表格也不多。处理起来应该还是挺方便的。

5.      最后根据上面的日志的信息,逐个表格处理。
建议处理步骤:
1 首先要根据 SQL语句,参数,确认一下是否真的有重复塞对应的表。
    如果查询出来的没有数据,则标识一下这个表是没有塞新的数据。
2 如果查询出来的有数据,则要先备份一下基础数据表(表名 +BAK),备份表要建立在 XYG下面。
    然后再根据多出来的数据,来清掉。
    例如:
    AP_1096_DATA_ALL
    对应的备份表应该是: XYG.XYG_AP_1096_DATA_ALL_BAK
    如果名字过长,则适当用简称。但是 XYG.XYG---BAK是必须要的关键字。
4 必须要查看对应表格是否有触发器。
3 备份后,然后再清理掉重复的数据。必须要注意,是重复的才处理!还有,业务实体 ID-3113-3114的不可以动。

(END)

今天和Oracle的工程师沟通上了。他们给的处理办法和我处理的结果几乎一样(都是针对这些表格来做数据的重复删除)。
而且这个文档,貌似在Oracle Meterlink上面是查询不到的(被保密,所以文档ID我就不说明了)。所以顺便贴一下,仅供交流用!
-----------
Applies to:
Oracle Payables - Version: 12.0.6 to 12.0.6
Oracle Receivables - Version: 12.0.6 to 12.0.6
Oracle Incentive Compensation - Version: 10.7
Goal
During system upgrade, running Replicate Seed Data Concurrent Program to populate the
Installed modules with Seeded values cause the following data issue:

Missing records in table AR_MEMO_LINES_ALL_TL and duplicated records in the following
tables:

AR_SYSTEM_PARAMETERS_ALL

AP_1096_DATA_ALL
AP_TOLERANCES_ALL

CN_CW_SETUP_TASKS_ALL_TL
CN_CW_SETUP_TASKS_ALL_B

The folloiwng errors show up when trying to enter the system options in AR(Account
Receivable):
ORA-01422: exact fetch returns more than requested number of rows

--------------------------------------------------------------
FRM-40735: POST-QUERY trigger raised unhandled exception ORA-01422.

Pressing OK. Trying to choose OU with following errors:

APP-AR-294467: System options already define for this OU.You can only
view or update this option.

When trying to Query system options with following errors:
ORA-01422: exact fetch returns more than requested number of rows
Solution
Account Receivable data fix
===================

1. AR_SYSTEM_PARAMETERS_ALL: Log an itar and provide the output of the following query.

SELECT ROWID, org_id, b.* FROM AR_SYSTEM_PARAMETERS_ALL b
WHERE org_id in (select org_id from AR_SYSTEM_PARAMETERS_ALL
group by org_id having count(*) > 1)
and 1 <= (SELECT Count(*)
FROM AR_SYSTEM_PARAMETERS_ALL a
WHERE a.org_id = b.org_id
AND a.ROWID < b.ROWID)
order by b.org_id;

2. AR_MEMO_LINES_ALL_TL: Run the following script to insert the missing records.
INSERT INTO AR_MEMO_LINES_ALL_TL
(MEMO_LINE_ID,
NAME,
DESCRIPTION,
LANGUAGE,
SOURCE_LANG,
LAST_UPDATE_DATE,
CREATION_DATE,
CREATED_BY,
LAST_UPDATED_BY,
LAST_UPDATE_LOGIN,
ORG_ID)
SELECT /*+ ordered no_expand parallel(a) */
MEMO_LINE_ID,
NAME,
DESCRIPTION,
LANGUAGE,
SOURCE_LANG,
LAST_UPDATE_DATE,
CREATION_DATE,
CREATED_BY,
LAST_UPDATED_BY,
LAST_UPDATE_LOGIN,
V.ORGANIZATION_ID
FROM AR_MEMO_LINES_ALL_TL A,
(SELECT /*+ no_merge */
ORGANIZATION_ID
FROM FND_PRODUCT_GROUPS, HR_OPERATING_UNITS
WHERE PRODUCT_GROUP_ID = 1
AND MULTI_ORG_FLAG = 'Y'
UNION
SELECT NULL
FROM DUAL
WHERE NOT EXISTS (SELECT NULL
FROM FND_PRODUCT_GROUPS
WHERE MULTI_ORG_FLAG = 'Y')) V
WHERE NVL(a.ORG_ID, -99) = NVL(-3113, -99)
AND NOT (a.ORG_ID = -3114 AND V.ORGANIZATION_ID IS NOT NULL)
AND (A.MEMO_LINE_ID,A.LANGUAGE, NVL(V.ORGANIZATION_ID, -99)) NOT IN
(SELECT /*+ hash_aj parallel(b) */
MEMO_LINE_ID, LANGUAGE, NVL(ORG_ID, -99)
FROM AR_MEMO_LINES_ALL_TL B
WHERE B.MEMO_LINE_ID = A.MEMO_LINE_ID
AND B.LANGUAGE = A.LANGUAGE
AND nvl(B.org_id,-99)=nvl(V.ORGANIZATION_ID,-99));


Incentive Compensation data fix
=====================

delete from CN_CW_SETUP_TASKS_ALL_B c1
where c1.rowid = (select max(rowid)
from CN_CW_SETUP_TASKS_ALL_B c2
where c1.org_id = c2.org_id
and c1.setup_task_code = c2.setup_task_code
and c1.workbench_item_code = c2.workbench_item_code
and c1.setup_task_sequence = c2.setup_task_sequence
and c1.setup_task_status = c2.setup_task_status
group by org_id, setup_task_code, workbench_item_code,
setup_task_sequence, setup_task_status
having count(1) > 1 ) ;

-- should have 15 records per org_id for cn_cw_setup_tasks_all_b

delete from CN_CW_SETUP_TASKS_ALL_TL c1
where c1.rowid = (select max(rowid)
from CN_CW_SETUP_TASKS_ALL_TL c2
where c1.org_id = c2.org_id
and c1.setup_task_code = c2.setup_task_code
and c1.language = c2.language
and c1.source_lang = c2.source_lang
and c1.setup_task_name = c2.setup_task_name
and c1.setup_task_description = c2.setup_task_description
group by org_id, setup_task_code, language,
source_lang, setup_task_name, setup_task_description
having count(1) > 1 ) ;

-- should have 15 records per org_id per language for cn_cw_setup_tasks_all_tl


Account Payables data fix
=================

Table AP_TOLERANCES_ALL has been obsoleted, and is not used anywhere any longer.

Table AP_1096_DATA_ALL is simply a container for the summary information for the 1099 suppliers, and the data gets deleted and re-created, on running the 1099 reports.

Please log a new itar if the duplicated data cause any other issue in AP.


The code fix is addressed by the following bugs:

Bug:8302210 (AD) - REPLICATE SEED DATA PROGRAM INSERT DUPLICATE ROWS IN AR_SYSTEM_APRAMETERS_ALL

Bug:7828394 (AR) - REPLICATE SEED DATA PROGRAM INSERT DUPLICATE ROWS IN AR_SYSTEM_APRAMETERS_ALL


来自:http://www.itpub.net/thread-1803604-1-12.html

package com.example.kucun2.entity.data; import android.os.Handler; import android.os.Looper; import android.util.Log; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 线程安全的异步操作列表 * * @param <T> 列表元素类型,需继承自 SynchronizableEntity * * @apiNote 提供异步添加/移除元素功能,支持UI监听器通知 * @threadSafe 内部使用 CopyOnWriteArrayList 保证线程安全 */ public class SynchronizedList<T extends SynchronizableEntity> { private static final String TAG = "SynchronizedList"; // 使用静态线程池提高效率 private static final ExecutorService ASYNC_EXECUTOR = Executors.newFixedThreadPool(4); // 主线程处理器 private final Handler mainHandler = new Handler(Looper.getMainLooper()); // 内部存储(线程安全) private final List<T> internalList = new CopyOnWriteArrayList<>(); // 列表变更监听器 private final List<ListChangeListener<T>> listeners = new ArrayList<>(); private Class<T> type; /** * 构造方法 * * @param type 列表元素类型(用于反射) */ public SynchronizedList(Class<T> type) { this.type = type; } /** * 获取列表元素类型 * * @return 元素类型Class对象 */ public Class<T> getType() { return type; } //================ 监听器接口 ================// /** * 列表变更监听器接口 * * @param <T> 元素类型 */ public interface ListChangeListener<T> { /** * 元素添加事件 * * @param item 添加的元素 * @param position 添加位置 */ void onItemAdded(T item, int position); /** * 元素移除事件 * * @param item 移除的元素 * @param position 移除位置 */ void onItemRemoved(T item, int position); /** * 元素更新事件 * * @param item 更新的元素 * @param position 更新位置 */ void onItemUpdated(T item, int position); /** * 同步开始事件 * * @param item 正在同步的元素 */ void onSyncStarted(T item); /** * 同步成功事件 * * @param item 同步成功的元素 */ void onSyncSuccess(T item); /** * 同步失败事件 * * @param item 同步失败的元素 * @param error 错误信息 */ void onSyncFailure(T item, String error); /** * 列表整体变更事件 */ void onListChanged(); } /** * 操作回调接口 * * @param <T> 元素类型 */ public interface OperationCallback<T> { /** * 操作成功回调 * * @param item 操作的元素 */ void onSuccess(T item); /** * 操作失败回调 * * @param item 操作的元素 * @param error 错误信息 */ void onFailure(T item, String error); } //================ 监听器管理 ================// /** * 添加列表变更监听器 * * @param listener 要添加的监听器 */ public void addListChangeListener(ListChangeListener<T> listener) { if (listener != null) { listeners.add(listener); } } /** * 移除列表变更监听器 * * @param listener 要移除的监听器 */ public void removeListChangeListener(ListChangeListener<T> listener) { listeners.remove(listener); } /** * 通知所有监听器(在主线程执行) * * @param action 要执行的通知动作 */ private void notifyListeners(Runnable action) { mainHandler.post(() -> { for (ListChangeListener<T> listener : listeners) { action.run(); } }); } //================ 列表合并操作 ================// /** * 合并外部列表到当前列表(不触发同步) * * @param externalList 外部列表集合 * * @apiNote 根据元素的id属性进行合并: * 1. 如果id存在则更新数据 * 2. 如果id不存在则添加新元素 * 3. 不会触发任何同步操作 */ public void mergeList(List<T> externalList) { if (externalList == null || externalList.isEmpty()) { return; } // 创建ID映射表用于快速查找 Map<Integer, T> idMap = new HashMap<>(); for (T item : internalList) { idMap.put(item.getId(), item); } // 记录需要通知的变化 List<Runnable> changeNotifications = new ArrayList<>(); for (T externalItem : externalList) { Integer id = externalItem.getId(); if (idMap.containsKey(id)) { // ID存在 - 更新现有元素 T existingItem = idMap.get(id); int position = internalList.indexOf(existingItem); // 更新元素数据(保留原始对象的引用) if (existingItem != null) { existingItem.updateFrom(externalItem); } // 记录更新通知 changeNotifications.add(() -> { for (ListChangeListener<T> listener : listeners) { listener.onItemUpdated(existingItem, position); } }); } else { // ID不存在 - 添加新元素 int position = internalList.size(); internalList.add(externalItem); // 记录添加通知 changeNotifications.add(() -> { for (ListChangeListener<T> listener : listeners) { listener.onItemAdded(externalItem, position); } }); // 添加到ID映射表 idMap.put(id, externalItem); } } // 批量通知变化 if (!changeNotifications.isEmpty()) { notifyListeners(() -> { for (Runnable notification : changeNotifications) { notification.run(); } // 最后通知列表整体变化 for (ListChangeListener<T> listener : listeners) { listener.onListChanged(); } }); } } /** * 异步合并外部列表到当前列表 * * @param externalList 外部列表集合 * * @apiNote 在后台线程执行合并操作,完成后在主线程通知UI */ public void mergeListAsync(List<T> externalList) { ASYNC_EXECUTOR.execute(() -> mergeList(externalList)); } //================ 异步操作 ================// /** * 异步添加元素 * * @param item 要添加的元素 * @param callback 操作结果回调 * * @implNote 1. 立即添加元素到列表(UI可见) * 2. 后台执行同步操作 * 3. 根据同步结果更新状态 */ public void addAsync(T item, OperationCallback<T> callback) { if (item == null || internalList.contains(item)) return; // 1. 预添加状态(UI可立即显示) int position = internalList.size(); internalList.add(item); // 通知UI元素添加和同步开始 notifyListeners(() -> { for (ListChangeListener<T> listener : listeners) { listener.onItemAdded(item, position); listener.onSyncStarted(item); } }); // 2. 后台执行同步 ASYNC_EXECUTOR.execute(() -> { // 使用"create"操作类型进行同步 item.sync("create", new SynchronizableEntity.SyncCallback() { @Override public void onSyncSuccess(SynchronizableEntity entity) { // 同步成功,通知监听器 notifyListeners(() -> { for (ListChangeListener<T> listener : listeners) { listener.onSyncSuccess((T) entity); listener.onListChanged(); } }); // 执行成功回调 if (callback != null) callback.onSuccess((T) entity); } @Override public void onSyncFailure(String error) { // 同步失败,回滚列表 internalList.remove(item); // 通知UI元素移除和同步失败 notifyListeners(() -> { for (ListChangeListener<T> listener : listeners) { listener.onSyncFailure((T) item, error); listener.onItemRemoved((T) item, position); } }); // 执行失败回调 if (callback != null) callback.onFailure((T) item, error); } }); }); } /** * 异步移除元素 * * @param item 要移除的元素 * @param callback 操作结果回调 * * @implNote 1. 立即从列表移除元素(UI可见) * 2. 后台执行同步操作 * 3. 根据同步结果决定是否回滚 */ public void removeAsync(T item, OperationCallback<T> callback) { if (!internalList.contains(item)) return; // 记录位置用于可能的回滚 int position = internalList.indexOf(item); internalList.remove(item); // 通知UI元素移除和同步开始 notifyListeners(() -> { for (ListChangeListener<T> listener : listeners) { listener.onItemRemoved(item, position); listener.onSyncStarted(item); } }); // 后台执行同步 ASYNC_EXECUTOR.execute(() -> { // 使用"delete"操作类型进行同步 item.sync("delete", new SynchronizableEntity.SyncCallback() { @Override public void onSyncSuccess(SynchronizableEntity entity) { // 同步成功,通知监听器 notifyListeners(() -> { for (ListChangeListener<T> listener : listeners) { listener.onSyncSuccess((T) entity); } }); // 执行成功回调 if (callback != null) callback.onSuccess((T) entity); } @Override public void onSyncFailure(String error) { // 同步失败,回滚列表 internalList.add(position, item); // 通知UI元素添加回和同步失败 notifyListeners(() -> { for (ListChangeListener<T> listener : listeners) { listener.onSyncFailure((T) item, error); listener.onItemAdded(item, position); } }); // 执行失败回调 if (callback != null) callback.onFailure((T) item, error); } }); }); } //================ 列表访问 ================// /** * 获取不可修改的列表视图 * * @return 不可修改的列表视图 * * @apiNote 用于RecyclerView等UI组件 */ public List<T> getViewList() { return Collections.unmodifiableList(internalList); } /** * 获取列表大小 * * @return 列表元素数量 */ public int size() { return internalList.size(); } /** * 获取指定位置的元素 * * @param index 元素位置 * @return 指定位置的元素 * * @throws IndexOutOfBoundsException 如果索引越界 */ public T get(int index) { return internalList.get(index); } } package com.example.kucun2.entity.data; import android.util.Log; import com.example.kucun2.function.MyAppFunction; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 可同步实体基类 * 提供实体到服务端的同步功能,包含自动重试机制和线程管理 */ public abstract class SynchronizableEntity implements EntityClassGrassrootsid { private static final String TAG = "SynchronizableEntity"; // 双重缓存结构:外层缓存类名,内层缓存字段名->Field对象 private static final Map<Class<?>, Map<String, Field>> CLASS_FIELD_CACHE = new HashMap<>(); // 网络请求线程池(静态共享) private static final ExecutorService NETWORK_EXECUTOR = Executors.newFixedThreadPool(4); /** * 获取指定类型操作的端点URL * * @param type 操作类型(如"create", "update", "delete"等) * @return 完整的端点URL路径 * * @apiNote 该方法通过资源键名拼接规则查找对应的URL资源 * @example 对于Product类的create操作,查找键为:"url_create_product" */ public String getEndpoint(String type) { // 构建资源键名:url_操作类型_类名小写 String key = "url_" + type + "_" + this.getClass().getSimpleName().toLowerCase(); return MyAppFunction.getStringResource("string", key); } //================ 核心同步方法 ================// /** * 同步实体到服务端(公开接口) * * @param type 操作类型(如"create", "update"等) * @param callback 同步结果回调接口 * * @implNote 内部调用私有方法实现带重试机制的同步 * @see #sync(String, SyncCallback, int) */ public void sync(String type, SyncCallback callback) { sync(type, callback, 0); // 初始重试次数为0 } /** * 带重试机制的同步实现(私有方法) * * @param type 操作类型 * @param callback 同步结果回调 * @param retryCount 当前重试次数 * * @implSpec 1. 构建完整端点URL * 2. 通过线程池提交网络请求 * 3. 处理成功/失败回调 */ private void sync(String type, SyncCallback callback, int retryCount) { // 构建完整端点URL String baseUrl = MyAppFunction.getStringResource("string", "url"); String endpoint = baseUrl + getEndpoint(type); Log.d(TAG, "同步端点: " + endpoint + ", 重试次数: " + retryCount); // 提交到线程池执行网络请求 NETWORK_EXECUTOR.execute(() -> { ApiClient.postJson(endpoint, this, new ApiClient.ApiCallback<SynchronizableEntity>() { @Override public void onSuccess(SynchronizableEntity responseData) { handleSyncSuccess(responseData, callback); } @Override public void onError(int statusCode, String error) { handleSyncError(type, statusCode, error, callback, retryCount); } }); }); } /** * 处理同步成功结果 * * @param responseData 服务端返回的实体数据 * @param callback 同步结果回调 * * @implNote 1. 更新实体ID * 2. 记录成功日志 * 3. 触发成功回调 */ private void handleSyncSuccess(SynchronizableEntity responseData, SyncCallback callback) { // 更新实体ID(如果服务端返回了新ID) if (responseData != null) { setId(responseData.getId()); Log.i(TAG, "同步成功, 新ID: " + responseData.getId()); } // 触发成功回调 if (callback != null) callback.onSyncSuccess(this); } /** * 处理同步错误(含重试逻辑) * * @param type 操作类型 * @param statusCode HTTP状态码(-1表示网络错误) * @param error 错误信息 * @param callback 同步结果回调 * @param retryCount 当前重试次数 * * @implSpec 1. 判断错误是否可重试(网络错误或5xx服务错误) * 2. 满足条件时进行指数退避重试 * 3. 达到最大重试次数后触发失败回调 * * @algorithm 使用指数退避算法:延迟时间 = 1000ms * 2^重试次数 */ private void handleSyncError(String type, int statusCode, String error, SyncCallback callback, int retryCount) { Log.e(TAG, "同步失败: " + error + ", 状态码: " + statusCode); // 判断是否可重试(网络错误或服务端5xx错误) boolean canRetry = statusCode == -1 || (statusCode >= 500 && statusCode < 600); // 满足重试条件(可重试错误且未达到最大重试次数) if (canRetry && retryCount < 3) { // 计算指数退避延迟时间 long delay = (long) (1000 * Math.pow(2, retryCount)); Log.w(TAG, "将在 " + delay + "ms 后重试"); try { // 当前线程休眠指定时间 Thread.sleep(delay); } catch (InterruptedException e) { // 恢复中断状态 Thread.currentThread().interrupt(); } // 递归调用进行重试(重试次数+1) sync(type, callback, retryCount + 1); } else { // 不可重试或达到最大重试次数,触发失败回调 if (callback != null) { String finalError = "同步失败: " + error; if (canRetry) finalError += " (重试失败)"; callback.onSyncFailure(finalError); } } } // 线程安全的对象复制方法 public void updateFrom(Object source) { if ( source == null) return; Class<?> targetClass = this.getClass(); Class<?> sourceClass = source.getClass(); // 获取或创建字段缓存 Map<String, Field> targetFields = getOrCreateFieldCache(targetClass); Map<String, Field> sourceFields = getOrCreateFieldCache(sourceClass); // 遍历源对象字段 for (Map.Entry<String, Field> entry : sourceFields.entrySet()) { String fieldName = entry.getKey(); Field sourceField = entry.getValue(); // 查找目标对象对应字段 Field targetField = targetFields.get(fieldName); if (targetField != null) { // 类型兼容性检查 if (isCompatibleTypes(sourceField.getType(), targetField.getType())) { try { // 复制字段值 Object value = sourceField.get(source); targetField.set(this, value); } catch (IllegalAccessException e) { // 处理异常,记录日志 } } } } } // 获取或创建类的字段缓存 private static Map<String, Field> getOrCreateFieldCache(Class<?> clazz) { // 双重检查锁确保线程安全 if (!CLASS_FIELD_CACHE.containsKey(clazz)) { synchronized (clazz) { if (!CLASS_FIELD_CACHE.containsKey(clazz)) { Map<String, Field> fieldMap = new HashMap<>(); // 递归获取所有字段(包括父类) for (Class<?> current = clazz; current != null; current = current.getSuperclass()) { for (Field field : current.getDeclaredFields()) { field.setAccessible(true); // 突破访问限制 fieldMap.put(field.getName(), field); } } CLASS_FIELD_CACHE.put(clazz, fieldMap); } } } return CLASS_FIELD_CACHE.get(clazz); } // 类型兼容性检查(支持自动装箱/拆箱) private static boolean isCompatibleTypes(Class<?> sourceType, Class<?> targetType) { // 处理基本类型和包装类的兼容性 if (sourceType.isPrimitive()) { sourceType = primitiveToWrapper(sourceType); } if (targetType.isPrimitive()) { targetType = primitiveToWrapper(targetType); } return targetType.isAssignableFrom(sourceType); } // 基本类型转包装类 private static Class<?> primitiveToWrapper(Class<?> primitiveType) { if (boolean.class.equals(primitiveType)) return Boolean.class; if (byte.class.equals(primitiveType)) return Byte.class; if (char.class.equals(primitiveType)) return Character.class; if (double.class.equals(primitiveType)) return Double.class; if (float.class.equals(primitiveType)) return Float.class; if (int.class.equals(primitiveType)) return Integer.class; if (long.class.equals(primitiveType)) return Long.class; if (short.class.equals(primitiveType)) return Short.class; if (void.class.equals(primitiveType)) return Void.class; return primitiveType; } //================ 回调接口 ================// /** * 同步操作回调接口 * * @implNote 使用方需实现此接口处理同步结果 */ public interface SyncCallback { /** * 同步成功回调 * * @param entity 同步后的实体对象(已更新ID) */ void onSyncSuccess(SynchronizableEntity entity); /** * 同步失败回调 * * @param error 失败原因描述 */ void onSyncFailure(String error); } } 返回数据类型指定基类会丢掉很多数据
最新发布
06-26
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值