# DatabaseODBCWrapper **Repository Path**: qulee/database-odbcwrapper ## Basic Information - **Project Name**: DatabaseODBCWrapper - **Description**: 数据库ODBC操作简单封装 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-01-24 - **Last Updated**: 2026-04-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # DatabaseODBCWrapper 使用说明 `DatabaseODBCWrapper` 是一个用于封装 ODBC 数据库操作的 C++ 类,支持执行 SQL 查询、参数化查询、BLOB 数据操作等功能。 --- ## 功能特点 - **支持多种 SQL 操作:** 支持 `SELECT`、`INSERT`、`UPDATE` 和 `DELETE` 等语句。 - **参数化查询:** 提供了绑定多种类型参数的接口,支持 `int`、`long`、`long long`、`float`、`double`、`string`、`SQL_TIMESTAMP_STRUCT`、`SQL_NUMERIC_STRUCT` 和 `NULL` 等类型,确保安全性。 - **BLOB 数据支持:** 读取和写入 BLOB(Binary Large Object)数据。 - **自动管理连接:** 支持自动检查并重新连接数据库,连接断开时自动重连。 - **详细的错误信息输出:** 提供 ODBC 错误信息打印功能,便于调试。 - **事务支持:** 提供完整的事务处理机制,支持开始事务、提交事务、回滚事务和查询事务状态。 - **数据库信息查询:** 支持获取数据库类型、当前schema、检查表是否存在等功能。 - **NUMERIC 类型处理:** 支持多种 NUMERIC/DECIMAL 类型处理模式(字符串、double、智能模式),保证精度或性能。 - **SQLite3 WAL 模式支持:** 针对 SQLite3 数据库提供 WAL(Write-Ahead Logging)模式支持和 checkpoint 管理。 - **跨平台支持:** 支持Windows和Linux系统。 - **多数据库适配:** 自动识别并适配 MS SQL Server、PostgreSQL、MySQL、SQLite3、Oracle 等数据库。 --- ## 安装与配置 ### ODBC依赖配置 在引入`DatabaseODBCWrapper`库之前,需要正确配置ODBC依赖。有两种方式: #### 方式一:手动定义ODBC变量(推荐用于自定义构建) 在CMakeLists.txt中,需要手动定义`ODBC_INCLUDE_DIRS`和`ODBC_LIBRARIES`变量: ```cmake # 手动设置ODBC库的位置 # Windows系统 if(WIN32) # Windows ODBC位置(通常不需要手动设置,但如果有自定义安装可以指定) set(ODBC_INCLUDE_DIRS "C:/Program Files/ODBC/include") set(ODBC_LIBRARIES odbc32 odbccp32) # Linux系统 else() # Linux ODBC位置 set(ODBC_INCLUDE_DIRS /usr/include /usr/local/include) set(ODBC_LIBRARIES odbc odbcinst) endif() # 引入DatabaseODBCWrapper库(方式一:直接包含源码) add_subdirectory(path/to/DatabaseODBCWrapper) # 链接到您的项目 target_link_libraries(YOUR_PROJECT_NAME DatabaseODBCWrapper) ``` #### 方式二:使用CPM ```cmake # 使用CPM # 如果CPM.cmake不存在则自动下载 if(NOT EXISTS "${CMAKE_SOURCE_DIR}/cmake/CPM.cmake") file(DOWNLOAD "https://github.com/cpm-cmake/CPM.cmake/releases/latest/download/CPM.cmake" "${CMAKE_SOURCE_DIR}/cmake/CPM.cmake") endif() include("${CMAKE_SOURCE_DIR}/cmake/CPM.cmake") # 手动设置ODBC库的位置(在引入DatabaseODBCWrapper前设置) if(WIN32) set(ODBC_INCLUDE_DIRS "C:/Program Files/ODBC/include") set(ODBC_LIBRARIES odbc32 odbccp32) else() set(ODBC_INCLUDE_DIRS /usr/include /usr/local/include) set(ODBC_LIBRARIES odbc odbcinst) endif() # 引入自己的库 CPMAddPackage( NAME DatabaseODBCWrapper GIT_REPOSITORY https://gitee.com/qulee/database-odbcwrapper.git GIT_TAG v1.2 # 替换为需要的版本 ) # 链接到您的项目 target_link_libraries(YOUR_PROJECT_NAME DatabaseODBCWrapper) ``` ### ODBC库安装 如果您的系统尚未安装ODBC开发库: - **Windows**:通常已预装ODBC支持 - **Ubuntu/Debian**:`sudo apt-get install unixodbc unixodbc-dev` - **CentOS/RHEL**:`sudo yum install unixODBC unixODBC-devel` - **Fedora**:`sudo dnf install unixODBC unixODBC-devel` - **OpenEuler**:`https://docs.opengauss.org/zh/docs/6.0.0/docs/GettingStarted/ODBC.html` --- ## 使用方法 ### 1. 包含头文件 在使用此类前,请确保在项目中包含 `DatabaseODBCWrapper.h`: ```cpp #include ``` ### 2. 创建实例 创建 `DatabaseODBCWrapper` 的实例时,需要提供数据库的连接字符串。 **MS SQL Server:** ```cpp std::string connectionString = "Driver={ODBC Driver 17 for SQL Server};Server=localhost;Database=mydb;UID=username;PWD=password;"; DatabaseODBCWrapper dbWrapper(connectionString); ``` **PostgreSQL:** ```cpp std::string connectionString = "DSN=PostgreSQL;UID=username;PWD=password;Database=mydb;"; DatabaseODBCWrapper dbWrapper(connectionString); ``` **MySQL:** ```cpp std::string connectionString = "Driver={MySQL ODBC 8.0 Driver};Server=localhost;Database=mydb;UID=username;PWD=password;"; DatabaseODBCWrapper dbWrapper(connectionString); ``` **通用DSN方式:** ```cpp std::string connectionString = "DSN=your_dsn_name;UID=your_username;PWD=your_password;"; DatabaseODBCWrapper dbWrapper(connectionString); ``` ### 3. 连接数据库 #### 3.1 连接数据库 调用 `connect` 方法连接数据库。 ```cpp if (!dbWrapper.connect()) { std::cerr << "数据库连接失败!" << std::endl; return -1; } ``` #### 3.2 检查连接状态 使用 `isConnected()` 方法检查连接是否正常,如果连接断开会自动尝试重连。 ```cpp if (!dbWrapper.isConnected()) { std::cerr << "数据库连接已断开!" << std::endl; } ``` #### 3.3 手动重新连接 如果需要手动重新连接,可以使用 `reconnect()` 方法。 ```cpp if (!dbWrapper.reconnect()) { std::cerr << "重新连接失败!" << std::endl; } ``` ### 4. 执行 SQL 语句 #### 4.1 执行非查询语句(INSERT、UPDATE、DELETE) ```cpp std::string sql = "UPDATE users SET age = 30 WHERE id = 1;"; SQLLEN affectedRows; if (!dbWrapper.executeNonQuery(sql, affectedRows)) { std::cerr << "SQL 执行失败!" << std::endl; } else { std::cout << "受影响的行数: " << affectedRows << std::endl; } ``` #### 4.2 执行查询语句(SELECT) ```cpp std::string sql = "SELECT id, name, age FROM users;"; DBResult results; if (!dbWrapper.executeQuery(sql, results)) { std::cerr << "查询失败!" << std::endl; } else { for (const auto& row : results) { std::cout << "ID: " << std::get(row.at("id")) << ", Name: " << std::get(row.at("name")) << ", Age: " << std::get(row.at("age")) << std::endl; } } ``` #### 4.3 执行参数化查询 **参数化非查询语句:** ```cpp std::string sql = "INSERT INTO users (name, age) VALUES (?, ?);"; std::vector params = {"John", 25}; SQLLEN affectedRows; if (!dbWrapper.executeParameterizedNonQuery(sql, params, affectedRows)) { std::cerr << "参数化 SQL 执行失败!" << std::endl; } else { std::cout << "受影响的行数: " << affectedRows << std::endl; } ``` **参数化查询语句:** ```cpp std::string sql = "SELECT id, name, age FROM users WHERE age > ?;"; std::vector params = {20}; DBResult results; if (!dbWrapper.executeParameterizedQuery(sql, params, results)) { std::cerr << "参数化查询失败!" << std::endl; } else { for (const auto& row : results) { std::cout << "ID: " << std::get(row.at("id")) << ", Name: " << std::get(row.at("name")) << ", Age: " << std::get(row.at("age")) << std::endl; } } ``` **支持的参数类型示例:** ```cpp // 基本类型 std::vector params1 = {42}; // int std::vector params2 = {100L}; // long std::vector params3 = {1000LL}; // long long std::vector params4 = {3.14f}; // float std::vector params5 = {3.14}; // double std::vector params6 = {std::string("Hello")}; // string std::vector params7 = {nullptr}; // NULL // 时间戳类型 SQL_TIMESTAMP_STRUCT timestamp; timestamp.year = 2024; timestamp.month = 1; timestamp.day = 15; timestamp.hour = 10; timestamp.minute = 30; timestamp.second = 45; timestamp.fraction = 0; std::vector params7 = {timestamp}; // NUMERIC 类型 SQL_NUMERIC_STRUCT numeric; memset(&numeric, 0, sizeof(SQL_NUMERIC_STRUCT)); numeric.precision = 10; numeric.scale = 2; numeric.sign = 1; // 1 = 正数, 0 = 负数 // 设置数值(需要将十进制数转换为字节数组,这里简化示例) std::vector params8 = {numeric}; // 混合类型示例 std::string sql = "INSERT INTO orders (user_id, amount, order_date, price) VALUES (?, ?, ?, ?);"; std::vector params = { 100, // int: user_id 99.99, // double: amount timestamp, // SQL_TIMESTAMP_STRUCT: order_date numeric // SQL_NUMERIC_STRUCT: price }; SQLLEN affectedRows; dbWrapper.executeParameterizedNonQuery(sql, params, affectedRows); ``` ### 5. 事务操作 ```cpp // 开始事务 if (!dbWrapper.beginTransaction()) { std::cerr << "开始事务失败!" << std::endl; return -1; } try { // 执行多个SQL操作 SQLLEN affectedRows; // 第一个操作 if (!dbWrapper.executeNonQuery("INSERT INTO users (name, age) VALUES ('张三', 30)", affectedRows)) throw std::runtime_error("插入用户记录失败"); // 第二个操作 if (!dbWrapper.executeNonQuery("UPDATE accounts SET balance = balance - 100 WHERE user_id = 1", affectedRows)) throw std::runtime_error("更新账户1失败"); // 第三个操作 if (!dbWrapper.executeNonQuery("UPDATE accounts SET balance = balance + 100 WHERE user_id = 2", affectedRows)) throw std::runtime_error("更新账户2失败"); // 所有操作成功,提交事务 if (!dbWrapper.commitTransaction()) throw std::runtime_error("提交事务失败"); std::cout << "转账操作成功完成!" << std::endl; } catch (const std::exception& e) { // 出现异常,回滚事务 std::cerr << "错误: " << e.what() << std::endl; dbWrapper.rollbackTransaction(); std::cerr << "已回滚所有操作!" << std::endl; } ``` ### 6. BLOB 数据操作 #### 6.1 读取 BLOB 数据 ```cpp std::string sql = "SELECT BlobData FROM blob_table;"; std::vector> blobData; if (!dbWrapper.readBlobData(sql, blobData)) { std::cerr << "读取 BLOB 数据失败!" << std::endl; } else { for (const auto& blob : blobData) { std::cout << "BLOB 大小: " << blob.size() << " 字节" << std::endl; } } ``` #### 6.2 写入 BLOB 数据 以下是一个完整的示例,演示了如何使用DatabaseODBCWrapper来批量更新数据库中的BLOB/bytea字段 **(postgres数据库批量写入有些问题,无法使用这个方法,只能使用单行写入,具体请参考example中的示例)** : ```cpp #include #include bool rsWritePileDataBlob(DatabaseODBCWrapper &dbPtr, int areaID, int rowStart, int rowEnd, const std::vector> &blobData) { bool rs = false; int startId = rowStart + 1; int endId = rowEnd; // 拼接批量更新 SQL std::string sqlUpdate = "UPDATE area_" + std::to_string(areaID) + "00"; sqlUpdate += " SET DataTime = now(), BlobData = CASE ListNo "; for (int i = startId; i <= endId; ++i) { sqlUpdate += "WHEN " + std::to_string(i) + " THEN ? "; } sqlUpdate += "END WHERE ListNo BETWEEN " + std::to_string(startId) + " AND " + std::to_string(endId) + ";"; rs = dbPtr.writeBlobData(sqlUpdate, blobData, startId, endId); if (rs) { printf("写入BLOB成功: rsWritePileDataBlob\n"); } else { printf("写入BLOB失败: rsWritePileDataBlob\n"); return false; } return true; } int main() { std::string connectionString = "DSN=MPPODBC;schema=huanghua_yd_schema;"; DatabaseODBCWrapper dbWrapper(connectionString, true); if (!dbWrapper.connect()) { std::cerr << "数据库连接失败!" << std::endl; return -1; } std::cout << "数据库连接成功!" << std::endl; std::vector> blobs = { {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}, {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A} }; rsWritePileDataBlob(dbWrapper, 1, 0, 3, blobs); return 0; } ``` **特别说明:** - 上述示例演示了如何批量更新多行数据的BLOB/bytea字段 - 对于PostgreSQL数据库,可以通过连接字符串中的schema参数指定schema,如示例中的`schema=huanghua_yd_schema` - 也可以在SQL语句中使用完整表名:`schema_name.table_name` - 库会自动处理Linux和Windows平台上BLOB/bytea参数绑定的差异 - 对于大型BLOB数据,建议分批处理以提高性能 ### 7. 数据库信息查询 #### 7.1 获取数据库类型 ```cpp std::string dbType = dbWrapper.getDatabaseType(); std::cout << "数据库类型: " << dbType << std::endl; // 输出示例: "postgresql", "microsoft sql server", "mysql", "sqlite" 等 ``` #### 7.2 获取当前 Schema ```cpp std::string schemaName; if (dbWrapper.getCurrentSchema(schemaName)) { std::cout << "当前 Schema: " << schemaName << std::endl; } ``` #### 7.3 检查表是否存在 ```cpp std::string tableName = "users"; std::string schemaName; if (dbWrapper.checkTableExists(tableName, schemaName)) { std::cout << "表 " << tableName << " 存在于 Schema: " << schemaName << std::endl; } else { std::cout << "表 " << tableName << " 不存在" << std::endl; } ``` #### 7.4 从 SQL 语句中提取表名 ```cpp std::string sql = "UPDATE users SET age = 30 WHERE id = 1"; std::string tableName = dbWrapper.extractTableName(sql); std::cout << "提取的表名: " << tableName << std::endl; // 输出: "users" ``` #### 7.5 检查事务状态 ```cpp if (dbWrapper.isInTransaction()) { std::cout << "当前正在事务中" << std::endl; } else { std::cout << "当前不在事务中" << std::endl; } ``` ### 8. NUMERIC 类型处理模式 对于数据库中的 `NUMERIC` 或 `DECIMAL` 类型字段,库提供了三种处理模式: ```cpp // 设置 NUMERIC 处理模式 dbWrapper.setNumericHandlingMode(DatabaseODBCWrapper::NumericHandlingMode::AsString); // 总是转换为字符串(保证精度) dbWrapper.setNumericHandlingMode(DatabaseODBCWrapper::NumericHandlingMode::AsDouble); // 总是转换为 double(可能丢失精度) dbWrapper.setNumericHandlingMode(DatabaseODBCWrapper::NumericHandlingMode::Smart); // 智能模式(默认):小精度转 double,高精度保持字符串 // 获取当前处理模式 auto mode = dbWrapper.getNumericHandlingMode(); ``` **模式说明:** - **AsString:** 所有 NUMERIC 类型都转换为字符串,保证精度,但使用不便。 - **AsDouble:** 所有 NUMERIC 类型都转换为 double,使用方便,但可能丢失精度。 - **Smart(默认):** 智能模式,精度 ≤ 15 位且小数位 ≤ 6 位的转换为 double,其他保持字符串格式。 ### 9. SQLite3 WAL 模式支持(仅适用于 SQLite3) 对于 SQLite3 数据库,库提供了 WAL(Write-Ahead Logging)模式支持,可以提高并发性能: ```cpp // 检查是否为 SQLite3 数据库 std::string dbType = dbWrapper.getDatabaseType(); if (dbType.find("sqlite") != std::string::npos) { // 启用 WAL 模式(连接时自动启用,也可手动调用) if (dbWrapper.enableWALMode()) { std::cout << "WAL 模式已启用" << std::endl; } // 检查当前日志模式 std::string journalMode = dbWrapper.getJournalMode(); std::cout << "当前日志模式: " << journalMode << std::endl; // 输出: "wal", "delete", "truncate" 等 // 配置自动 checkpoint(每 N 次提交后执行 checkpoint) dbWrapper.setWALCheckpointOnCommit(100); // 每 100 次提交后执行 checkpoint // 手动执行 checkpoint dbWrapper.walCheckpoint(false); // false = TRUNCATE 模式,true = RESTART 模式 // 获取当前 checkpoint 配置 int interval = dbWrapper.getWALCheckpointInterval(); std::cout << "Checkpoint 间隔: " << interval << std::endl; } ``` **注意:** WAL 模式相关功能仅对 SQLite3 数据库有效,其他数据库类型调用这些方法会返回失败。 ### 10. 断开数据库连接 使用完成后,可以调用 `disconnect` 方法断开数据库连接。析构函数会自动调用 `disconnect()`,确保资源正确释放。 ```cpp dbWrapper.disconnect(); ``` --- ## 注意事项 ### ⚠️ 线程安全性 **本类不是线程安全的。** `DatabaseODBCWrapper` 类的实例不应在多个线程间共享。 **正确的多线程使用方式:** ```cpp // ✅ 正确:每个线程创建独立的实例 void workerThread(const std::string& connectionString) { // 每个线程创建自己的数据库对象 auto db = std::make_shared(connectionString); if (!db->connect()) { return; } // 安全使用,不会与其他线程冲突 DBResult results; db->executeQuery("SELECT * FROM table", results); // 线程结束时自动断开连接 } // ❌ 错误:多个线程共享同一个实例 auto sharedDb = std::make_shared(connectionString); thread1: sharedDb->executeQuery(...); // 线程不安全! thread2: sharedDb->executeQuery(...); // 线程不安全! ``` **原因:** - 类内部使用成员变量(如 `m_paramIndicators`、`m_inTransaction` 等)存储状态 - 多个线程同时访问同一个实例会导致数据竞争和未定义行为 - ODBC 句柄(`m_env`、`m_dbc`)不是线程安全的 **多线程建议:** - 每个线程维护独立的 `DatabaseODBCWrapper` 实例 - 如果需要在多线程间共享,请使用互斥锁(`std::mutex`)保护所有数据库操作 - 或者使用连接池模式,每个线程从池中获取独立的连接对象 ### 其他注意事项 1. **错误处理:** 建议在每次操作前检查连接状态,确保连接正常。库会自动检测连接断开并尝试重连。 2. **BLOB 数据大小限制:** 请根据数据库设置调整缓冲区大小。对于大型 BLOB 数据,建议分批处理。 3. **日志输出:** 设置构造函数中的 `infoOut` 参数为 `true` 可启用 SQL 和错误信息打印,便于调试。 4. **跨平台兼容性:** 在Linux系统上使用时,确保正确设置了ODBC库的路径。库会自动处理平台差异。 5. **多数据库兼容性:** 库已针对MS SQL Server、PostgreSQL、MySQL、SQLite3、Oracle等主流数据库进行了优化,会自动检测数据库类型并使用相应的SQL语法。 6. **参数类型:** 参数化查询支持的类型包括:`int`、`long`、`long long`、`float`、`double`、`std::string`、`SQL_TIMESTAMP_STRUCT`、`SQL_NUMERIC_STRUCT` 和 `nullptr`(NULL值)。注意:`float` 类型会映射到数据库的 `REAL`/`FLOAT` 类型,`double` 类型会映射到 `DOUBLE` 类型,使用时请根据数据库字段类型选择合适的类型以避免精度问题。 7. **资源管理:** 析构函数会自动断开连接并释放资源,但建议显式调用 `disconnect()` 以确保及时释放。 ## 版本更新 ### v2.1 - **修复MS SQL Server兼容性问题:** 解决了在连接MS SQL Server时报错"current_schema不是可以识别的内置函数名"的问题 - **增强多数据库支持:** 针对不同数据库类型(MS SQL Server、PostgreSQL、MySQL、Oracle)使用专门的SQL语法 - **新增数据库类型检测:** 添加`getDatabaseType()`方法,自动识别数据库类型 - **优化schema查询:** 针对每种数据库使用最佳的schema查询方式,提高兼容性和可靠性 - **修复编译警告:** 解决了所有类型转换和模板实例化警告,提高代码质量 - **增强输入验证:** 添加了对SQL语句、参数数量等的严格验证,提高稳定性 - **改进错误处理:** 增强了连接状态检查、资源释放和异常情况处理 - **提升内存安全性:** 优化了字符串处理、边界检查和资源管理,避免潜在的内存安全问题 --- ## 参考 - [ODBC 官方文档](https://learn.microsoft.com/en-us/sql/odbc/) - 数据库连接字符串格式:[ConnectionStrings.com](https://www.connectionstrings.com/)