PostgreSQL - C / C++
PostgreSQL - 现代 C++ 接口与 libpqxx
Section titled “PostgreSQL - 现代 C++ 接口与 libpqxx”本教程介绍 libpqxx,它是 PostgreSQL 官方的 C++ 客户端 API。我们将重点关注现代 C++ 实践(C++17 及更高版本)以及使用 CMake 的现代化开发工作流。
安装与项目设置(现代方法)
Section titled “安装与项目设置(现代方法)”手动下载和构建库是一种过时做法。现代 C++ 项目使用包管理器,如 vcpkg 或 Conan,或者系统自带的包管理器。我们将使用 CMake 构建系统,它是行业标准。
1. 安装 libpqxx
Section titled “1. 安装 libpqxx”使用你的系统包管理器。例如:
# 在 Debian/Ubuntu 上sudo apt-get install libpqxx-dev
# 在 macOS 上使用 Homebrewbrew install libpqxx
# 在 Windows 上使用 vcpkgvcpkg install libpqxx:x64-windows2. 配置 pg_hba.conf
Section titled “2. 配置 pg_hba.conf”确保你的 pg_hba.conf 文件允许本地连接。对于开发环境,类似下面这样一行配置很常见。在你的 PostgreSQL 数据目录中找到此文件。
# 类型 数据库 用户 地址 方法# IPv4 本地连接:host all all 127.0.0.1/32 scram-sha-256注意:md5 是一种较旧的方法;scram-sha-256 是现代密码认证的默认方式。进行更改后,请重启 PostgreSQL 服务器。
libpqxx 核心概念
Section titled “libpqxx 核心概念”pqxx::connection:表示到数据库的连接。它应该被创建一次,并且可以是长期存在的。pqxx::work:一个事务对象。所有修改数据的 SQL 命令都应在一个事务中执行。它确保原子性(Atomicity):要么所有命令都成功,要么所有命令都不成功。pqxx::nontransaction:一个非事务对象,通常用于只读的SELECT查询,以获得略微更好的性能。- 预处理语句 (Prepared Statements):执行相同查询多次但参数不同的最安全、最有效的方式。它们可以防止 SQL 注入攻击。
完整示例:CRUD 操作
Section titled “完整示例:CRUD 操作”让我们创建一个 main.cpp 文件,演示完整的连接和 CRUD(创建、读取、更新、删除)生命周期。
main.cpp
Section titled “main.cpp”#include <iostream>#include <stdexcept>#include <pqxx/pqxx>
int main() { // 最佳实践:使用连接字符串。避免硬编码凭据。 // 考虑从环境变量或配置文件中加载。 const std::string conn_string = "dbname=testdb user=postgres password=pass123 hostaddr=127.0.0.1 port=5432";
try { pqxx::connection c{conn_string}; std::cout << "Opened database successfully: " << c.dbname() << std::endl;
// --- 创建表 (在事务中) --- { pqxx::work w{c}; w.exec0( "CREATE TABLE IF NOT EXISTS employees (" "id SERIAL PRIMARY KEY, " "name TEXT NOT NULL UNIQUE, " "salary INT);" ); w.commit(); std::cout << "Table 'employees' created or already exists." << std::endl; }
// --- 使用预处理语句插入 (最佳实践) --- { pqxx::work w{c}; c.prepare("insert_employee", "INSERT INTO employees (name, salary) VALUES ($1, $2)");
w.exec_prepared("insert_employee", "Alice", 95000); w.exec_prepared("insert_employee", "Bob", 80000); std::cout << "Inserted Alice and Bob via prepared statement." << std::endl;
// 使用 ON CONFLICT 进行“插入或更新”操作 (upsert) w.exec0("INSERT INTO employees (name, salary) VALUES ('Alice', 96000) " "ON CONFLICT (name) DO UPDATE SET salary = EXCLUDED.salary;"); std::cout << "Upserted Alice's salary." << std::endl; w.commit(); }
// --- 查询 (只读,可以使用 nontransaction) --- { pqxx::nontransaction n{c}; std::cout << "\n--- Current Employees --- " << std::endl; for (auto const &[id, name, salary] : n.query<int, std::string, int>("SELECT id, name, salary FROM employees ORDER BY name")) { std::cout << "ID: " << id << ", Name: " << name << ", Salary: " << salary << std::endl; } }
// --- 更新和删除 --- { pqxx::work w{c}; // 更新 Bob 的薪水 pqxx::result res = w.exec_params("UPDATE employees SET salary = $1 WHERE name = $2", 85000, "Bob"); std::cout << "\nUpdated " << res.affected_rows() << " row(s)." << std::endl;
// 删除 Alice res = w.exec_params("DELETE FROM employees WHERE name = $1", "Alice"); std::cout << "Deleted " << res.affected_rows() << " row(s)." << std::endl; w.commit(); }
c.close(); std::cout << "\nOperation done successfully." << std::endl;
} catch (const pqxx::sql_error &e) { std::cerr << "SQL error: " << e.what() << std::endl; std::cerr << "Query was: " << e.query() << std::endl; return 1; } catch (const std::exception &e) { std::cerr << "Error: " << e.what() << std::endl; return 1; }
return 0;}使用 CMake 构建
Section titled “使用 CMake 构建”在 main.cpp 所在的目录中创建一个 CMakeLists.txt 文件。
CMakeLists.txt
Section titled “CMakeLists.txt”cmake_minimum_required(VERSION 3.15)project(PostgresExample CXX)
set(CMAKE_CXX_STANDARD 17)set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 查找 libpqxx 库find_package(libpqxx REQUIRED)
add_executable(app main.cpp)
# 链接 libpqxx 及其依赖项 (libpq)target_link_libraries(app PRIVATE pqxx::pqxx)现在,从你的终端编译并运行项目:
# 创建构建目录mkdir buildcd build
# 使用 CMake 配置项目cMAKE ..
# 构建可执行文件cMAKE --build .
# 运行应用程序./app