This commit is contained in:
2026-04-10 13:47:53 +08:00
commit 8c78c0f920
61 changed files with 30343 additions and 0 deletions

View File

@@ -0,0 +1,342 @@
批量保存到数据库是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()`方法刷新地图视图,确保用户看到最新的数据状态。视图更新操作通过回调机制通知地图重绘,与批量保存操作解耦,保证了数据持久化和视图显示的独立性。