Files
JChargePointProtocol/docs/数据模型与数据库设计.md
2025-10-28 14:39:06 +08:00

398 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 数据模型与数据库设计
<cite>
**本文档引用的文件**
- [Station.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Station.java)
- [Pile.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Pile.java)
- [Gun.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Gun.java)
- [Attribute.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Attribute.java)
- [schema-init.sql](file://jcpp-app/src/main/resources/sql/schema-init.sql)
- [StationMapper.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/StationMapper.java)
- [PileMapper.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/PileMapper.java)
- [GunMapper.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/GunMapper.java)
- [AttributeMapper.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/AttributeMapper.java)
- [AttributeMapper.xml](file://jcpp-app/src/main/resources/mapper/AttributeMapper.xml)
- [AttrKeyEnum.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/AttrKeyEnum.java)
- [PileTypeEnum.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/PileTypeEnum.java)
- [StatusCleanupInitializingBean.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/initializing/StatusCleanupInitializingBean.java)
- [SqlBlockingQueueWrapper.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/SqlBlockingQueueWrapper.java)
</cite>
## 目录
1. [核心实体数据模型](#核心实体数据模型)
2. [实体关系与ER图](#实体关系与er图)
3. [属性实体设计](#属性实体设计)
4. [数据库表结构与索引](#数据库表结构与索引)
5. [数据访问层设计](#数据访问层设计)
6. [数据生命周期管理](#数据生命周期管理)
## 核心实体数据模型
### 充电站 (Station)
充电站实体表示物理充电站点,包含站点的基本信息和地理位置。
**属性说明:**
- `id`: UUID类型主键全局唯一标识符
- `createdTime`: 时间戳,记录创建时间
- `updatedTime`: 时间戳,记录最后更新时间
- `additionalInfo`: JSON类型存储额外的扩展信息
- `stationName`: 字符串类型,站点名称,非空
- `stationCode`: 字符串类型,站点编码,全局唯一,非空
- `longitude`: 浮点类型,经度坐标
- `latitude`: 浮点类型,纬度坐标
- `province`: 字符串类型,省份信息
- `city`: 字符串类型,城市信息
- `county`: 字符串类型,区县信息
- `address`: 字符串类型,详细地址
- `version`: 整数类型,版本号,用于乐观锁控制
**Section sources**
- [Station.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Station.java#L1-L65)
### 充电桩 (Pile)
充电桩实体表示充电站内的具体充电设备,与充电站存在一对多关系。
**属性说明:**
- `id`: UUID类型主键全局唯一标识符
- `createdTime`: 时间戳,记录创建时间
- `updatedTime`: 时间戳,记录最后更新时间
- `additionalInfo`: JSON类型存储额外的扩展信息
- `pileName`: 字符串类型,充电桩名称,非空
- `pileCode`: 字符串类型,充电桩编码,全局唯一,非空
- `protocol`: 字符串类型,通信协议类型,非空
- `stationId`: UUID类型外键关联到充电站实体
- `brand`: 字符串类型,品牌信息
- `model`: 字符串类型,型号信息
- `manufacturer`: 字符串类型,制造商信息
- `type`: 枚举类型充电桩类型交流AC/直流DC非空
- `version`: 整数类型,版本号,用于乐观锁控制
**Section sources**
- [Pile.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Pile.java#L1-L64)
- [PileTypeEnum.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/PileTypeEnum.java#L1-L22)
### 充电枪 (Gun)
充电枪实体表示充电桩上的具体充电接口,与充电桩存在一对多关系。
**属性说明:**
- `id`: UUID类型主键全局唯一标识符
- `createdTime`: 时间戳,记录创建时间
- `updatedTime`: 时间戳,记录最后更新时间
- `additionalInfo`: 字符串类型,存储额外的扩展信息
- `gunNo`: 字符串类型,枪编号,在同一充电桩下唯一,非空
- `gunName`: 字符串类型,充电枪名称,非空
- `gunCode`: 字符串类型,充电枪编码,全局唯一,非空
- `stationId`: UUID类型外键关联到充电站实体
- `pileId`: UUID类型外键关联到充电桩实体
- `version`: 整数类型,版本号,用于乐观锁控制
**Section sources**
- [Gun.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Gun.java#L1-L56)
## 实体关系与ER图
充电站、充电桩和充电枪之间存在明确的层次结构关系:一个充电站包含多个充电桩,一个充电桩包含多个充电枪。
```mermaid
erDiagram
STATION {
uuid id PK
timestamp created_time
timestamp updated_time
jsonb additional_info
varchar station_name
varchar station_code UK
double precision longitude
double precision latitude
varchar province
varchar city
varchar county
varchar address
int version
}
PILE {
uuid id PK
timestamp created_time
timestamp updated_time
jsonb additional_info
varchar pile_name
varchar pile_code UK
varchar protocol
uuid station_id FK
varchar brand
varchar model
varchar manufacturer
varchar type
int version
}
GUN {
uuid id PK
timestamp created_time
timestamp updated_time
varchar additional_info
varchar gun_no
varchar gun_name
varchar gun_code UK
uuid station_id FK
uuid pile_id FK
int version
}
STATION ||--o{ PILE : "包含"
PILE ||--o{ GUN : "包含"
```
**Diagram sources**
- [Station.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Station.java#L1-L65)
- [Pile.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Pile.java#L1-L64)
- [Gun.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Gun.java#L1-L56)
- [schema-init.sql](file://jcpp-app/src/main/resources/sql/schema-init.sql#L45-L130)
## 属性实体设计
### Attribute实体架构
Attribute实体采用灵活的键值对K-V存储模式用于存储设备的动态属性如电压、电流、状态等实时数据。
**表结构设计特点:**
- **复合主键**: 由`entity_id``attr_key`组成,确保每个实体的每个属性键唯一
- **多值类型字段**: 为支持不同数据类型,表中包含多种类型的值字段
- **时间戳**: `last_update_ts`记录属性最后更新时间
- **版本控制**: `version`字段用于乐观锁控制
**多值类型字段:**
- `bool_v`: 布尔值存储true/false类型数据
- `str_v`: 字符串值存储文本数据最大长度10,000,000字符
- `long_v`: 长整型值,存储大整数
- `dbl_v`: 双精度值,存储浮点数
- `json_v`: JSON值存储复杂结构化数据
**Section sources**
- [Attribute.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Attribute.java#L1-L102)
### 属性与核心实体的关联
Attribute实体通过`entity_id`字段与Station、Pile和Gun实体关联实现一对多的关系。
```mermaid
erDiagram
STATION ||--o{ ATTRIBUTE : "拥有"
PILE ||--o{ ATTRIBUTE : "拥有"
GUN ||--o{ ATTRIBUTE : "拥有"
ATTRIBUTE {
uuid entity_id PK,FK
varchar attr_key PK
boolean bool_v
varchar str_v
bigint long_v
double precision dbl_v
json json_v
bigint last_update_ts
int version
}
```
**属性键枚举 (AttrKeyEnum)**
系统定义了标准的属性键枚举,确保属性命名的一致性:
- `STATUS`: 状态
- `CONNECTED_AT`: 连接时间
- `DISCONNECTED_AT`: 断开连接时间
- `LAST_ACTIVE_TIME`: 最后活跃时间
- `GUN_RUN_STATUS`: 充电枪运行状态
- `LOCK_STATUS`: 地锁状态
- `PARK_STATUS`: 车位状态
**Diagram sources**
- [Attribute.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Attribute.java#L1-L102)
- [AttrKeyEnum.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/AttrKeyEnum.java#L1-L69)
## 数据库表结构与索引
### 表结构详情
根据`schema-init.sql`文件,数据库包含以下主要表:
**t_station (充电站表)**
- 主键: `id`
- 唯一索引: `uni_station_code` on `station_code`
- 字段: id, created_time, updated_time, additional_info, station_name, station_code, longitude, latitude, province,
city, county, address, version
**t_pile (充电桩表)**
- 主键: `id`
- 外键: `station_id` references `t_station(id)`
- 唯一索引: `uni_pile_code` on `pile_code`
- 字段: id, created_time, updated_time, additional_info, pile_name, pile_code, protocol, station_id, brand, model,
manufacturer, type, version
**t_gun (充电枪表)**
- 主键: `id`
- 外键: `station_id` references `t_station(id)`, `pile_id` references `t_pile(id)`
- 唯一索引: `uni_gun_pile_gun_no` on (`pile_id`, `gun_no`), `uni_gun_code` on `gun_code`
- 字段: id, created_time, updated_time, additional_info, gun_no, gun_name, gun_code, station_id, pile_id, version
**t_attr (属性表)**
- 复合主键: (`entity_id`, `attr_key`)
- 字段: entity_id, attr_key, bool_v, str_v, long_v, dbl_v, json_v, last_update_ts, version
### 索引设计与查询优化
数据库设计了多个索引以优化查询性能:
**唯一性约束索引:**
- `uni_station_code`: 确保充电站编码全局唯一
- `uni_pile_code`: 确保充电桩编码全局唯一
- `uni_gun_code`: 确保充电枪编码全局唯一
- `uni_gun_pile_gun_no`: 确保同一充电桩下枪编号唯一
**查询性能优化:**
这些索引不仅保证了数据的完整性,还显著提升了基于编码的查询性能。例如,通过充电桩编码查询充电桩信息时,可以直接利用
`uni_pile_code`索引进行快速查找。
**Section sources**
- [schema-init.sql](file://jcpp-app/src/main/resources/sql/schema-init.sql#L1-L130)
## 数据访问层设计
### MyBatis Mapper接口设计
数据访问层采用MyBatis框架通过Mapper接口与XML映射文件配合工作。
**核心Mapper接口**
- `StationMapper`: 继承自`BaseMapper<Station>`提供基础的CRUD操作
- `PileMapper`: 扩展了基础操作,包含自定义查询方法
- `GunMapper`: 扩展了基础操作,包含自定义查询方法
- `AttributeMapper`: 提供属性数据的特定访问方法
### Mapper接口与XML映射配合
Mapper接口定义了数据访问方法而具体的SQL语句在对应的XML文件中实现这种分离设计提高了代码的可维护性。
**PileMapper示例**
```java
IPage<PileWithStatusResponse> selectPileWithStatusPage(
Page<PileWithStatusResponse> page,
@Param("request") PileQueryRequest request,
@Param("statusKey") AttrKeyEnum statusKey,
@Param("connectedAtKey") AttrKeyEnum connectedAtKey,
@Param("disconnectedAtKey") AttrKeyEnum disconnectedAtKey,
@Param("lastActiveTimeKey") AttrKeyEnum lastActiveTimeKey
);
```
对应的SQL在`PileMapper.xml`中定义但项目中使用了注解方式直接在接口中定义SQL体现了MyBatis-Plus的特性。
**AttributeMapper XML映射**
```xml
<mapper namespace="sanbing.jcpp.app.dal.mapper.AttributeMapper">
<select id="findByEntity" resultType="sanbing.jcpp.app.dal.entity.Attribute">
SELECT * FROM t_attr WHERE entity_id = #{entityId}
</select>
<select id="findByEntityAndKey" resultType="sanbing.jcpp.app.dal.entity.Attribute">
SELECT * FROM t_attr WHERE entity_id = #{entityId} AND attr_key = #{attrKey}
</select>
<delete id="deleteByEntityIdAndKey">
DELETE FROM t_attr WHERE entity_id = #{entityId} AND attr_key = #{attrKey}
</delete>
</mapper>
```
这种设计模式的优势:
1. **关注点分离**: Java接口定义方法签名XML文件定义SQL实现
2. **可维护性**: SQL语句集中管理便于修改和优化
3. **类型安全**: MyBatis通过接口提供类型安全的数据访问
4. **灵活性**: 复杂SQL可以在XML中使用动态SQL标签构建
**Section sources**
- [StationMapper.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/StationMapper.java#L1-L15)
- [PileMapper.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/PileMapper.java#L1-L77)
- [GunMapper.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/GunMapper.java#L1-L131)
- [AttributeMapper.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/AttributeMapper.java#L1-L53)
- [AttributeMapper.xml](file://jcpp-app/src/main/resources/mapper/AttributeMapper.xml#L1-L43)
## 数据生命周期管理
### 状态清洗与初始化
系统在启动时执行状态清洗操作,确保充电桩状态的一致性。
**StatusCleanupInitializingBean组件**
- 在Spring容器初始化时执行
- 执行充电桩状态的全量清洗
- 如果失败会阻止应用启动,确保数据状态一致性
- 分页处理充电桩每次处理1000个避免内存溢出
**状态决策逻辑:**
根据会话状态、数据库状态和超时时间决定目标状态:
- 检查充电桩是否有活跃的会话连接
- 结合当前数据库状态和配置的超时阈值
- 决定最终的目标状态(在线/离线)
**Section sources**
- [StatusCleanupInitializingBean.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/initializing/StatusCleanupInitializingBean.java#L1-L169)
### 批量数据处理与队列机制
系统实现了高效的批量数据处理机制,用于优化数据库写入性能。
**SqlBlockingQueueWrapper设计**
- 使用阻塞队列实现数据批量处理
- 支持多线程并行处理
- 可配置批处理大小和最大延迟时间
- 提供统计信息打印功能
**批处理参数:**
- `logName`: 日志名称
- `batchSize`: 批处理大小
- `maxDelay`: 最大延迟时间(毫秒)
- `statsPrintIntervalMs`: 统计信息打印间隔
- `statsNamePrefix`: 统计名称前缀
- `batchSortEnabled`: 是否启用批处理排序
- `withResponse`: 是否需要响应
**数据处理流程:**
1. 数据元素被添加到阻塞队列
2. 单线程消费者定期从队列中获取数据
3. 当达到批处理大小或超时后,执行批量保存
4. 支持对批处理数据进行排序,避免集群模式下的死锁
**Section sources**
- [SqlBlockingQueueWrapper.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/SqlBlockingQueueWrapper.java#L1-L58)
- [SqlBlockingQueue.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/SqlBlockingQueue.java#L1-L58)
- [SqlBlockingQueueParams.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/SqlBlockingQueueParams.java#L1-L24)
- [ScheduledLogExecutorComponent.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/ScheduledLogExecutorComponent.java#L1-L40)