1. 项目概述
在现代C++应用开发中,数据访问层的设计质量直接影响系统的可维护性和扩展性。Repository模式作为一种经典的数据访问抽象模式,能够有效隔离业务逻辑与数据持久化细节。本文将基于实际项目经验,深入讲解如何在C++中实现一个类型安全、高性能的ORM风格数据访问层。
这个方案已经在AIDC自动气象站数据收集系统中得到验证,支撑日均百万级的数据库操作。我们将从设计理念到具体实现,完整呈现一个工业级Repository模式的构建过程。
2. 为什么需要Repository模式
2.1 传统数据访问方式的问题
在早期项目中,我们经常看到这样的代码:
cpp复制// 业务逻辑中直接嵌入SQL
void updateStationStatus() {
auto conn = getConnection();
auto stmt = conn->prepare("UPDATE stations SET status=? WHERE id=?");
stmt->bind(1, newStatus);
stmt->bind(2, stationId);
stmt->execute();
// 其他业务逻辑...
}
这种方式的痛点非常明显:
- 维护困难:SQL语句散落在各处,修改表结构需要全局搜索
- 难以测试:业务逻辑与数据库强耦合,无法进行单元测试
- 重复代码:相同的CRUD操作在不同地方重复实现
- 切换成本高:更换数据库需要修改大量SQL语句
2.2 Repository模式的优势
Repository模式通过引入抽象层,带来了显著改进:
| 优势 | 具体表现 |
|---|---|
| 关注点分离 | 业务逻辑不再关心数据存储细节 |
| 可测试性 | 可以轻松Mock数据访问层进行单元测试 |
| 统一接口 | 所有数据访问通过标准化接口进行 |
| 类型安全 | 编译期检查取代运行时SQL错误 |
| 代码复用 | 通用操作在基类中实现,避免重复 |
3. Repository模式架构设计
3.1 整体架构设计
我们的Repository实现采用分层架构:
code复制业务逻辑层
↓ 调用
Repository接口层
↓ 依赖
ORM实现层
↓ 使用
数据库驱动层
关键设计决策:
- 使用模板实现通用Repository基类
- 具体仓储类继承基类并添加领域特定方法
- 采用轻量级ORM作为底层实现
- 连接池统一管理数据库连接
3.2 核心类设计
cpp复制// 通用Repository模板
template<typename T>
class Repository {
public:
virtual Result<int> Insert(const T& entity);
virtual Result<std::vector<T>> QueryAll();
// 其他CRUD操作...
};
// 具体仓储类
class StationRepository : public Repository<StationInfo> {
public:
std::optional<StationInfo> FindByStationNum(string num);
std::vector<StationInfo> FindActiveStations();
// 其他领域特定方法...
};
4. 核心代码实现
4.1 基础Repository模板
cpp复制template<typename T>
class Repository {
public:
// 插入记录
Result<int> Insert(const T& entity) {
auto& db = DbManager::Instance().GetDb();
try {
return db.insert(entity);
} catch (const DbException& e) {
return ErrorCode::DB_OPERATION_FAILED;
}
}
// 条件查询
template<typename Predicate>
Result<std::vector<T>> QueryWhere(Predicate pred) {
auto& db = DbManager::Instance().GetDb();
return db.query<T>().where(pred).execute();
}
// 其他CRUD操作...
};
关键实现细节:
- 使用模板实现类型安全
- 所有方法返回Result类型显式处理错误
- 内部使用轻量级ORM执行实际数据库操作
- 异常转换为错误码返回
4.2 具体仓储类实现
cpp复制class StationRepository : public Repository<StationInfo> {
public:
std::optional<StationInfo> FindByStationNum(string num) {
auto result = QueryWhere([&](const StationInfo& s) {
return s.station_num == num;
});
return result ? result.Value().first_or_null() : std::nullopt;
}
std::vector<StationInfo> FindActiveStations() {
auto result = QueryWhere([](const StationInfo& s) {
return s.status == Status::Active;
});
return result ? result.Value() : std::vector<StationInfo>{};
}
};
5. CRUD操作详解
5.1 创建操作
cpp复制// 单个插入
StationInfo station{"54511", "北京站", 39.9, 116.4};
auto result = repo.Insert(station);
// 批量插入
std::vector<StationInfo> stations = {...};
auto result = repo.BatchInsert(stations.begin(), stations.end());
最佳实践:
- 批量操作使用事务保证原子性
- 考虑使用连接池获取连接
- 返回Result类型强制错误检查
5.2 查询操作
cpp复制// 简单查询
auto all = repo.QueryAll();
// 条件查询
auto active = repo.QueryWhere([](const StationInfo& s) {
return s.status == Status::Active;
});
// 获取单个实体
auto station = repo.FindByStationNum("54511");
6. 复杂查询实现
6.1 分页查询
cpp复制template<typename T>
class PaginatedRepository : public Repository<T> {
public:
struct PageResult {
std::vector<T> data;
int total;
int page;
int pageSize;
};
template<typename Predicate>
PageResult QueryPage(Predicate pred, int page, int pageSize) {
int offset = (page - 1) * pageSize;
auto data = this->QueryWhere(pred).limit(pageSize).offset(offset);
int total = this->CountWhere(pred);
return {data, total, page, pageSize};
}
};
6.2 关联查询
cpp复制class StationDeviceRepository {
public:
struct StationDevices {
StationInfo station;
std::vector<DeviceInfo> devices;
};
std::optional<StationDevices> GetStationWithDevices(string stationNum) {
auto station = stationRepo.FindByStationNum(stationNum);
if (!station) return std::nullopt;
auto devices = deviceRepo.QueryWhere([&](const DeviceInfo& d) {
return d.station_num == stationNum;
});
return StationDevices{*station, devices};
}
};
7. 性能优化策略
7.1 连接池优化
cpp复制class DbManager {
public:
static DbManager& Instance() {
static DbManager instance;
return instance;
}
DbConnection& GetDb() {
return pool_.getConnection();
}
private:
ConnectionPool pool_{10}; // 10个连接
};
7.2 缓存策略
cpp复制template<typename T>
class CachedRepository : public Repository<T> {
public:
std::optional<T> FindByIdCached(int id) {
auto it = cache_.find(id);
if (it != cache_.end() && !it->second.expired()) {
return it->second.value();
}
auto entity = this->FindById(id);
if (entity) {
cache_[id] = CacheEntry{*entity};
}
return entity;
}
private:
std::unordered_map<int, CacheEntry> cache_;
};
8. 最佳实践
8.1 错误处理
cpp复制auto result = repo.FindByStationNum("54511");
if (!result) {
switch (result.Error()) {
case ErrorCode::DB_CONNECTION_FAILED:
// 处理连接错误
break;
case ErrorCode::DB_QUERY_FAILED:
// 处理查询错误
break;
default:
// 处理未知错误
}
return;
}
// 使用查询结果
auto station = result.Value();
8.2 事务管理
cpp复制void TransferData() {
auto& db = DbManager::Instance().GetDb();
db.beginTransaction();
try {
repo1.Update(...);
repo2.Delete(...);
db.commit();
} catch (...) {
db.rollback();
throw;
}
}
8.3 单元测试
cpp复制TEST(StationRepositoryTest, FindActiveStations) {
MockDatabase mockDb;
StationRepository repo(mockDb);
// 设置mock预期
EXPECT_CALL(mockDb, query(_))
.WillOnce(Return(testStations));
auto result = repo.FindActiveStations();
ASSERT_EQ(3, result.size());
}
9. 实际应用经验
在实际项目中应用这套方案时,我们总结了以下经验:
-
接口设计要稳定:Repository接口一旦确定,修改成本很高,前期需要充分设计
-
性能监控很重要:所有数据库操作都应该记录执行时间,便于发现性能瓶颈
-
缓存一致性:使用缓存时要特别注意数据一致性,可以采用主动失效策略
-
连接泄漏检查:定期检查数据库连接是否正常释放
-
批量操作优化:大数据量操作要考虑分批处理,避免长时间占用连接
这套实现已经在生产环境稳定运行2年多,支撑着日均百万级的数据库操作。相比之前的直接SQL方式,新系统的可维护性和稳定性都有了显著提升。
