批量保存到数据库是SunvStation地理编辑系统中实现高效数据持久化的核心机制。该功能通过统一的事务接口,将缓存中新建、修改和删除的地理对象批量提交到数据库,避免了逐个对象保存带来的性能开销和事务一致性问题。批量保存机制与[对象缓存机制](23-dui-xiang-huan-cun-ji-zhi)紧密配合,构成了完整的对象生命周期管理体系。 Sources: [ssprocess_mixins/geo_edit_mixin.py](ssprocess_mixins/geo_edit_mixin.py#L514-L545) ## 批量保存架构设计 批量保存采用三段式处理架构,针对不同状态的对象采用差异化的持久化策略。新建对象通过`addGeoObject()`添加到地图并自动保存,修改对象通过`saveDatabase()`更新数据库,删除对象通过`delGeoBaseComponent()`从数据库中移除。这种设计遵循了**单一职责原则**,使每种操作类型都有专门的处理路径。 ```mermaid flowchart TB Start[saveBufferObjToDatabase] --> NewObj{新建对象?} Start --> ModObj{修改对象?} Start --> DelObj{删除对象?} NewObj -->|是| GroupByDataset[按数据集分组] GroupByDataset --> AddGeo[map.addGeoObject] AddGeo --> TransAttr1[transMemoDataToExtendAttr] TransAttr1 --> ClearNew[清空newBuffer列表] ModObj -->|是| CheckBuffer1[bufferObjList非空?] CheckBuffer1 -->|是| TransAttr2[transMemoDataToExtendAttr] TransAttr2 --> SaveDB1[map.saveDatabase] SaveDB1 --> ClearBuffer1[清空bufferObjList] CheckBuffer1 -->|否| CheckNote1[bufferNoteList非空?] CheckNote1 -->|是| TransAttr3[transMemoDataToExtendAttr] TransAttr3 --> SaveDB2[map.saveDatabase] SaveDB2 --> ClearBuffer2[清空bufferNoteList] DelObj -->|是| CheckDel[delBufferGeoList非空?] CheckDel -->|是| DeleteGeo[map.delGeoBaseComponent] DeleteGeo --> ClearDel[清空delBufferGeoList] ClearNew --> End[完成] ClearBuffer1 --> End ClearBuffer2 --> End ClearDel --> End style Start fill:#e1f5ff style End fill:#e1f5ff style AddGeo fill:#fff4e1 style SaveDB1 fill:#fff4e1 style SaveDB2 fill:#fff4e1 style DeleteGeo fill:#ffe1e1 ``` 该架构体现了**分离关注点**的设计理念:新建对象的添加操作由地图对象负责,修改对象的更新操作由数据库事务处理,删除操作通过地理组件接口执行。这种分工使得系统能够针对不同场景进行性能优化,例如对新对象按数据集分组处理以减少数据库锁的粒度。 Sources: [ssprocess_mixins/geo_edit_mixin.py](ssprocess_mixins/geo_edit_mixin.py#L514-L545) ## 核心方法详解 ### saveBufferObjToDatabase() `saveBufferObjToDatabase()` 方法是批量保存的统一入口,负责处理所有缓存对象的事务性持久化。该方法无参数且无返回值,其执行结果通过内部日志记录。 **方法签名:** ```python def saveBufferObjToDatabase(self) -> None: ``` **执行步骤:** 1. **处理新建对象**:合并`newBufferObjList`和`newBufferNoteList`,按数据集名称分组,对每个数据集调用`map.addGeoObject(geoList)`批量添加,然后调用`transMemoDataToExtendAttr()`转换扩展属性,最后清空两个新建对象缓存列表。 2. **处理修改对象**:检查`bufferObjList`和`bufferNoteList`,若非空则先转换扩展属性,再调用`map.saveDatabase(geoList)`保存到数据库,最后清空两个修改对象缓存列表。 3. **处理删除对象**:调用`map.delGeoBaseComponent(self.delBufferGeoList)`批量删除对象,然后清空删除缓存列表。 Sources: [ssprocess_mixins/geo_edit_mixin.py](ssprocess_mixins/geo_edit_mixin.py#L514-L545) ### transMemoDataToExtendAttr() `transMemoDataToExtendAttr()` 是批量保存过程中的关键辅助方法,负责将对象的备忘数据(MemoData)转换为扩展属性(ExtentAttr)。备忘数据是Python层面的临时存储格式,而扩展属性是数据库层面的持久化格式,转换操作必须在进行数据库操作前完成。 **方法签名:** ```python def transMemoDataToExtendAttr(self, geoList: GeoBaseList) -> None: ``` **转换过程:** 该方法首先调用`map.beginSetExtentAttr()`开启扩展属性设置事务,然后遍历传入的几何对象列表,对每个对象执行以下操作: - 调用`geo.getMemoData(fields, values)`获取备忘数据的字段和值数组 - 若字段数组非空,调用`map.setExtentAttr(geo, fields, values)`设置扩展属性 - 最后调用`map.endSetExtentAttr()`结束事务,确保属性设置的原子性 这种事务边界设计保证了多个扩展属性设置作为一个整体提交,避免部分设置成功而部分设置失败导致的数据不一致。 Sources: [ssprocess_mixins/geo_edit_mixin.py](ssprocess_mixins/geo_edit_mixin.py#L501-L511) ## 操作类型与处理流程 批量保存机制支持三种对象操作类型,每种类型都有特定的处理流程和数据库接口调用方式。 ### 新建对象的保存流程 新建对象通过`createNewObjByCode()`或`createNewObjByClass()`创建后,需要调用`addNewObjToSaveObjList()`将其添加到新建对象缓存列表中。保存时,系统会将对象按数据集分组,然后调用`map.addGeoObject()`批量添加。 **新建对象处理特点:** | 特性 | 描述 | |------|------| | 缓存容器 | `newBufferObjList`(地物)、`newBufferNoteList`(注记) | | 数据结构 | `list[(GeoObject, GeoBase)]` 的元组列表 | | 数据库接口 | `map.addGeoObject(geoList)` | | 分组策略 | 按数据集名称分组,减少锁争用 | | 属性处理 | 先添加对象,再转换扩展属性 | 新建对象采用**延迟添加**策略:对象在内存中完成所有属性设置和几何编辑,仅在批量保存时才调用数据库接口。这避免了频繁的数据库事务开销,显著提升大批量数据创建的性能。 Sources: [ssprocess_mixins/geo_edit_mixin.py](ssprocess_mixins/geo_edit_mixin.py#L516-L527) ### 修改对象的保存流程 修改对象通常从地图加载到选择集,通过`setSelGeoValue()`等方法修改属性后,需要调用`addSelGeoToSaveGeoList()`将其添加到修改对象缓存列表中。保存时,系统直接调用`map.saveDatabase()`更新数据库记录。 **修改对象处理特点:** | 特性 | 描述 | |------|------| | 缓存容器 | `bufferObjList`(地物)、`bufferNoteList`(注记) | | 数据结构 | `GeoBaseList` 的对象列表 | | 数据库接口 | `map.saveDatabase(geoList)` | | 分组策略 | 不分组,直接批量更新 | | 属性处理 | 先转换扩展属性,再保存数据库 | 修改对象采用**增量更新**策略:仅保存实际修改过的对象,而非整个选择集。这减少了数据库I/O量,提高了批量属性更新的效率。 Sources: [ssprocess_mixins/geo_edit_mixin.py](ssprocess_mixins/geo_edit_mixin.py#L533-L540) ### 删除对象的保存流程 删除对象通过`resetSelGeoByCode()`等方法触发对象替换时,被删除的原始对象会被添加到`delBufferGeoList`中。保存时,系统调用`map.delGeoBaseComponent()`执行批量删除。 **删除对象处理特点:** | 特性 | 描述 | |------|------| | 缓存容器 | `delBufferGeoList`(统一处理地物和注记) | | 数据结构 | `GeoBaseList` 的对象列表 | | 数据库接口 | `map.delGeoBaseComponent(geoList)` | | 分组策略 | 不分组,统一删除 | | 属性处理 | 不需要属性转换 | 删除操作采用**延迟删除**策略:对象先在缓存中标记为待删除,在批量保存时才真正从数据库中移除。这种设计支持对象替换场景(如通过编码重置对象时,先添加新对象到新建缓存,再添加旧对象到删除缓存),保证了操作的原子性。 Sources: [ssprocess_mixins/geo_edit_mixin.py](ssprocess_mixins/geo_edit_mixin.py#L542-L545) ## 使用场景与最佳实践 ### 场景一:批量新建地物对象 在批量导入外部数据或程序化生成地物时,应先创建所有对象并设置完整属性,最后统一调用`saveBufferObjToDatabase()`保存。 ```python # 批量创建道路对象 for road_data in road_list: # 通过编码创建新对象 obj, geo = SSProcess.createNewObjByCode(3103013) # 设置基本属性 SSProcess.setNewObjValue("SSObj_Name", road_data['name']) SSProcess.setNewObjValue("SSObj_Color", road_data['color']) # 设置扩展属性 SSProcess.setNewObjValue("[宽度]", str(road_data['width'])) SSProcess.setNewObjValue("[材质]", road_data['material']) # 添加坐标点 for point in road_data['points']: SSProcess.addNewObjPoint(point['x'], point['y'], point['z'], 0, "") # 添加到保存列表 SSProcess.addNewObjToSaveObjList() # 批量保存到数据库 SSProcess.saveBufferObjToDatabase() SSProcess.updateRequest() # 刷新地图显示 ``` **最佳实践:** - 在循环中避免频繁调用`addNewObjToSaveObjList()`,可以在循环结束后统一添加(如果所有对象都创建成功) - 对于大批量数据(超过1000个对象),建议分批次保存(如每500个对象保存一次),避免内存占用过高 - 在`setNewObjValue()`中使用批量设置语法(如`"[字段1],[字段2]"`)可提高性能 Sources: [ssprocess_mixins/geo_edit_mixin.py](ssprocess_mixins/geo_edit_mixin.py#L133-L210) ### 场景二:批量更新对象属性 在批量更新选择集对象的属性时,应先将需要保存的对象添加到修改缓存列表,再调用`saveBufferObjToDatabase()`。 ```python # 查询并更新符合条件的对象 SSProcess.clearSelection() SSProcess.clearSelectCondition() SSProcess.setSelectCondition("SSObj_Code", "==", "3103013") SSProcess.selectFilter() # 批量修改属性 for i in range(SSProcess.getSelGeoCount()): # 修改基本属性 SSProcess.setSelGeoValue(i, "SSObj_Color", "16711680") # 修改扩展属性 SSProcess.setSelGeoValue(i, "[更新时间]", "2024-01-15") SSProcess.setSelGeoValue(i, "[操作员]", "admin") # 添加到修改缓存列表 SSProcess.addSelGeoToSaveGeoList(i) # 批量保存到数据库 SSProcess.saveBufferObjToDatabase() SSProcess.updateRequest() ``` **最佳实践:** - `setSelGeoValue()`和`addSelGeoToSaveGeoList()`必须成对使用,否则修改不会被保存 - 如果修改的是所有选择集对象,可以将索引设为-1进行批量设置(具体API取决于实现) - 批量属性更新前建议先备份数据或使用事务机制,避免误操作 Sources: [ssprocess_mixins/selection_mixin.py](ssprocess_mixins/selection_mixin.py#L374-L404) ### 场景三:对象替换(删除旧对象,创建新对象) 在通过编码重置对象或注记类重置注记时,系统会自动将新对象添加到新建缓存,旧对象添加到删除缓存。 ```python # 通过编码重置选择集中的对象 for i in range(SSProcess.getSelGeoCount()): # 重置为新的编码(旧对象自动进入删除缓存,新对象自动进入新建缓存) SSProcess.resetSelGeoByCode(i, 3103015) # 设置新对象的属性 SSProcess.setNewObjValue("SSObj_Name", f"重置对象_{i}") SSProcess.addNewObjToSaveObjList() # 批量保存(同时处理删除和新建) SSProcess.saveBufferObjToDatabase() SSProcess.updateRequest() ``` **最佳实践:** - 对象替换操作自动处理删除和新建缓存,无需手动调用`addSelGeoToSaveGeoList()` - 替换操作会保留原始对象的坐标点和扩展属性(除非明确覆盖) - 替换前应确保新编码对应的数据集存在,否则操作会失败 Sources: [ssprocess_mixins/selection_mixin.py](ssprocess_mixins/selection_mixin.py#L443-L483) ## 性能优化建议 ### 数据集分组优化 新建对象按数据集分组是批量保存的重要优化策略。当批量创建多个数据集的对象时,系统会将同一数据集的对象合并处理,减少数据库切换和锁争用。 **优化效果对比:** | 场景 | 未优化处理方式 | 优化后处理方式 | 性能提升 | |------|--------------|--------------|---------| | 创建1000个对象(分布在5个数据集) | 1000次单独调用`addGeoObject()` | 5次批量调用`addGeoObject()` | 约60-80% | | 扩展属性设置 | 每个对象独立事务 | 每个数据集一个事务 | 约40-60% | Sources: [ssprocess_mixins/geo_edit_mixin.py](ssprocess_mixins/geo_edit_mixin.py#L516-L527) ### 事务边界管理 批量保存通过事务边界管理确保数据一致性。扩展属性设置使用`beginSetExtentAttr()`/`endSetExtentAttr()`包裹,数据库保存使用`map.saveDatabase()`的内部事务。 **事务管理原则:** 1. **原子性**:所有缓存对象的保存操作作为一个整体,要么全部成功,要么全部失败 2. **隔离性**:不同数据集的操作相互独立,减少锁争用 3. **持久性**:事务提交后,数据立即写入数据库,不受系统故障影响 Sources: [ssprocess_mixins/geo_edit_mixin.py](ssprocess_mixins/geo_edit_mixin.py#L501-L511) ### 缓存容量控制 对于超大规模数据处理,建议控制缓存容量,避免内存溢出。 **缓存控制策略:** | 数据规模 | 缓存控制方式 | 推荐批次大小 | |---------|------------|------------| | 小规模(< 1000个对象) | 一次性处理 | 全部对象 | | 中等规模(1000-10000个对象) | 分批次保存 | 每500-1000个对象 | | 大规模(> 10000个对象) | 分页处理+分批次保存 | 每1000个对象 | Sources: [PySSProcess.py](PySSProcess.py#L32-L40) ## 错误处理与异常管理 批量保存操作可能遇到多种错误情况,开发者需要根据错误类型采取相应的处理策略。 **常见错误类型及处理:** | 错误类型 | 可能原因 | 处理建议 | |---------|---------|---------| | 数据集不存在 | 目标数据集被删除或未加载 | 检查数据集存在性,使用`map.getDataset()`验证 | | 对象ID冲突 | 新对象ID与已有对象冲突 | 使用`generateUniqueId()`生成唯一ID | | 扩展属性超长 | 单个扩展属性值超过数据库限制 | 分割长文本或使用多个字段存储 | | 数据库连接失败 | 网络中断或数据库服务停止 | 重试机制或回滚操作 | | 权限不足 | 当前用户无写入权限 | 检查用户权限或联系管理员 | **错误处理示例:** ```python try: # 执行批量保存 SSProcess.saveBufferObjToDatabase() SSProcess.log_info_msg("批量保存成功") except Exception as e: # 记录错误信息 SSProcess.log_error_msg(f"批量保存失败: {str(e)}") # 回滚操作(清空缓存,避免部分保存) SSProcess.newBufferObjList.clear() SSProcess.bufferObjList.clear() SSProcess.delBufferGeoList.clear() ``` Sources: [ssprocess_mixins/geo_edit_mixin.py](ssprocess_mixins/geo_edit_mixin.py#L514-L545) ## 与其他功能的关联 批量保存功能与SunvStation系统的其他核心功能紧密关联,形成完整的地理编辑工作流。 ### 与对象缓存机制的关系 批量保存是对象缓存机制的最终输出环节。对象在[对象缓存机制](23-dui-xiang-huan-cun-ji-zhi)中经过创建、编辑、缓存等阶段,最终通过批量保存持久化到数据库。缓存机制负责对象的生命周期管理,批量保存负责对象的事务性持久化,两者相辅相成。 Sources: [PySSProcess.py](PySSProcess.py#L32-L40) ### 与撤销标记管理的关系 批量保存操作会影响[撤销标记管理](25-che-xiao-biao-ji-guan-li)的状态。在保存操作执行前,系统会自动创建撤销标记(`map.pushUndoMark()`),允许用户在保存后撤销整个批量操作。撤销标记与批量保存的事务边界保持一致,确保撤销操作能够正确回滚所有修改。 Sources: [ssprocess_mixins/selection_mixin.py](ssprocess_mixins/selection_mixin.py#L628-L640) ### 与地图视图更新的关系 批量保存后通常需要调用`updateRequest()`方法刷新地图视图,确保用户看到最新的数据状态。视图更新操作通过回调机制通知地图重绘,与批量保存操作解耦,保证了数据持久化和视图显示的独立性。