16 KiB
批量保存到数据库是SunvStation地理编辑系统中实现高效数据持久化的核心机制。该功能通过统一的事务接口,将缓存中新建、修改和删除的地理对象批量提交到数据库,避免了逐个对象保存带来的性能开销和事务一致性问题。批量保存机制与对象缓存机制紧密配合,构成了完整的对象生命周期管理体系。
Sources: ssprocess_mixins/geo_edit_mixin.py
批量保存架构设计
批量保存采用三段式处理架构,针对不同状态的对象采用差异化的持久化策略。新建对象通过addGeoObject()添加到地图并自动保存,修改对象通过saveDatabase()更新数据库,删除对象通过delGeoBaseComponent()从数据库中移除。这种设计遵循了单一职责原则,使每种操作类型都有专门的处理路径。
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
核心方法详解
saveBufferObjToDatabase()
saveBufferObjToDatabase() 方法是批量保存的统一入口,负责处理所有缓存对象的事务性持久化。该方法无参数且无返回值,其执行结果通过内部日志记录。
方法签名:
def saveBufferObjToDatabase(self) -> None:
执行步骤:
-
处理新建对象:合并
newBufferObjList和newBufferNoteList,按数据集名称分组,对每个数据集调用map.addGeoObject(geoList)批量添加,然后调用transMemoDataToExtendAttr()转换扩展属性,最后清空两个新建对象缓存列表。 -
处理修改对象:检查
bufferObjList和bufferNoteList,若非空则先转换扩展属性,再调用map.saveDatabase(geoList)保存到数据库,最后清空两个修改对象缓存列表。 -
处理删除对象:调用
map.delGeoBaseComponent(self.delBufferGeoList)批量删除对象,然后清空删除缓存列表。
Sources: ssprocess_mixins/geo_edit_mixin.py
transMemoDataToExtendAttr()
transMemoDataToExtendAttr() 是批量保存过程中的关键辅助方法,负责将对象的备忘数据(MemoData)转换为扩展属性(ExtentAttr)。备忘数据是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
操作类型与处理流程
批量保存机制支持三种对象操作类型,每种类型都有特定的处理流程和数据库接口调用方式。
新建对象的保存流程
新建对象通过createNewObjByCode()或createNewObjByClass()创建后,需要调用addNewObjToSaveObjList()将其添加到新建对象缓存列表中。保存时,系统会将对象按数据集分组,然后调用map.addGeoObject()批量添加。
新建对象处理特点:
| 特性 | 描述 |
|---|---|
| 缓存容器 | newBufferObjList(地物)、newBufferNoteList(注记) |
| 数据结构 | list[(GeoObject, GeoBase)] 的元组列表 |
| 数据库接口 | map.addGeoObject(geoList) |
| 分组策略 | 按数据集名称分组,减少锁争用 |
| 属性处理 | 先添加对象,再转换扩展属性 |
新建对象采用延迟添加策略:对象在内存中完成所有属性设置和几何编辑,仅在批量保存时才调用数据库接口。这避免了频繁的数据库事务开销,显著提升大批量数据创建的性能。
Sources: ssprocess_mixins/geo_edit_mixin.py
修改对象的保存流程
修改对象通常从地图加载到选择集,通过setSelGeoValue()等方法修改属性后,需要调用addSelGeoToSaveGeoList()将其添加到修改对象缓存列表中。保存时,系统直接调用map.saveDatabase()更新数据库记录。
修改对象处理特点:
| 特性 | 描述 |
|---|---|
| 缓存容器 | bufferObjList(地物)、bufferNoteList(注记) |
| 数据结构 | GeoBaseList 的对象列表 |
| 数据库接口 | map.saveDatabase(geoList) |
| 分组策略 | 不分组,直接批量更新 |
| 属性处理 | 先转换扩展属性,再保存数据库 |
修改对象采用增量更新策略:仅保存实际修改过的对象,而非整个选择集。这减少了数据库I/O量,提高了批量属性更新的效率。
Sources: ssprocess_mixins/geo_edit_mixin.py
删除对象的保存流程
删除对象通过resetSelGeoByCode()等方法触发对象替换时,被删除的原始对象会被添加到delBufferGeoList中。保存时,系统调用map.delGeoBaseComponent()执行批量删除。
删除对象处理特点:
| 特性 | 描述 |
|---|---|
| 缓存容器 | delBufferGeoList(统一处理地物和注记) |
| 数据结构 | GeoBaseList 的对象列表 |
| 数据库接口 | map.delGeoBaseComponent(geoList) |
| 分组策略 | 不分组,统一删除 |
| 属性处理 | 不需要属性转换 |
删除操作采用延迟删除策略:对象先在缓存中标记为待删除,在批量保存时才真正从数据库中移除。这种设计支持对象替换场景(如通过编码重置对象时,先添加新对象到新建缓存,再添加旧对象到删除缓存),保证了操作的原子性。
Sources: ssprocess_mixins/geo_edit_mixin.py
使用场景与最佳实践
场景一:批量新建地物对象
在批量导入外部数据或程序化生成地物时,应先创建所有对象并设置完整属性,最后统一调用saveBufferObjToDatabase()保存。
# 批量创建道路对象
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
场景二:批量更新对象属性
在批量更新选择集对象的属性时,应先将需要保存的对象添加到修改缓存列表,再调用saveBufferObjToDatabase()。
# 查询并更新符合条件的对象
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
场景三:对象替换(删除旧对象,创建新对象)
在通过编码重置对象或注记类重置注记时,系统会自动将新对象添加到新建缓存,旧对象添加到删除缓存。
# 通过编码重置选择集中的对象
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
性能优化建议
数据集分组优化
新建对象按数据集分组是批量保存的重要优化策略。当批量创建多个数据集的对象时,系统会将同一数据集的对象合并处理,减少数据库切换和锁争用。
优化效果对比:
| 场景 | 未优化处理方式 | 优化后处理方式 | 性能提升 |
|---|---|---|---|
| 创建1000个对象(分布在5个数据集) | 1000次单独调用addGeoObject() |
5次批量调用addGeoObject() |
约60-80% |
| 扩展属性设置 | 每个对象独立事务 | 每个数据集一个事务 | 约40-60% |
Sources: ssprocess_mixins/geo_edit_mixin.py
事务边界管理
批量保存通过事务边界管理确保数据一致性。扩展属性设置使用beginSetExtentAttr()/endSetExtentAttr()包裹,数据库保存使用map.saveDatabase()的内部事务。
事务管理原则:
- 原子性:所有缓存对象的保存操作作为一个整体,要么全部成功,要么全部失败
- 隔离性:不同数据集的操作相互独立,减少锁争用
- 持久性:事务提交后,数据立即写入数据库,不受系统故障影响
Sources: ssprocess_mixins/geo_edit_mixin.py
缓存容量控制
对于超大规模数据处理,建议控制缓存容量,避免内存溢出。
缓存控制策略:
| 数据规模 | 缓存控制方式 | 推荐批次大小 |
|---|---|---|
| 小规模(< 1000个对象) | 一次性处理 | 全部对象 |
| 中等规模(1000-10000个对象) | 分批次保存 | 每500-1000个对象 |
| 大规模(> 10000个对象) | 分页处理+分批次保存 | 每1000个对象 |
Sources: PySSProcess.py
错误处理与异常管理
批量保存操作可能遇到多种错误情况,开发者需要根据错误类型采取相应的处理策略。
常见错误类型及处理:
| 错误类型 | 可能原因 | 处理建议 |
|---|---|---|
| 数据集不存在 | 目标数据集被删除或未加载 | 检查数据集存在性,使用map.getDataset()验证 |
| 对象ID冲突 | 新对象ID与已有对象冲突 | 使用generateUniqueId()生成唯一ID |
| 扩展属性超长 | 单个扩展属性值超过数据库限制 | 分割长文本或使用多个字段存储 |
| 数据库连接失败 | 网络中断或数据库服务停止 | 重试机制或回滚操作 |
| 权限不足 | 当前用户无写入权限 | 检查用户权限或联系管理员 |
错误处理示例:
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
与其他功能的关联
批量保存功能与SunvStation系统的其他核心功能紧密关联,形成完整的地理编辑工作流。
与对象缓存机制的关系
批量保存是对象缓存机制的最终输出环节。对象在对象缓存机制中经过创建、编辑、缓存等阶段,最终通过批量保存持久化到数据库。缓存机制负责对象的生命周期管理,批量保存负责对象的事务性持久化,两者相辅相成。
Sources: PySSProcess.py
与撤销标记管理的关系
批量保存操作会影响撤销标记管理的状态。在保存操作执行前,系统会自动创建撤销标记(map.pushUndoMark()),允许用户在保存后撤销整个批量操作。撤销标记与批量保存的事务边界保持一致,确保撤销操作能够正确回滚所有修改。
Sources: ssprocess_mixins/selection_mixin.py
与地图视图更新的关系
批量保存后通常需要调用updateRequest()方法刷新地图视图,确保用户看到最新的数据状态。视图更新操作通过回调机制通知地图重绘,与批量保存操作解耦,保证了数据持久化和视图显示的独立性。