Skip to content

PostgreSQL - C / C++

本教程介绍 libpqxx,它是 PostgreSQL 官方的 C++ 客户端 API。我们将重点关注现代 C++ 实践(C++17 及更高版本)以及使用 CMake 的现代化开发工作流。

手动下载和构建库是一种过时做法。现代 C++ 项目使用包管理器,如 vcpkg 或 Conan,或者系统自带的包管理器。我们将使用 CMake 构建系统,它是行业标准。

使用你的系统包管理器。例如:

# 在 Debian/Ubuntu 上
sudo apt-get install libpqxx-dev
# 在 macOS 上使用 Homebrew
brew install libpqxx
# 在 Windows 上使用 vcpkg
vcpkg install libpqxx:x64-windows

确保你的 pg_hba.conf 文件允许本地连接。对于开发环境,类似下面这样一行配置很常见。在你的 PostgreSQL 数据目录中找到此文件。

# 类型 数据库 用户 地址 方法
# IPv4 本地连接:
host all all 127.0.0.1/32 scram-sha-256

注意:md5 是一种较旧的方法;scram-sha-256 是现代密码认证的默认方式。进行更改后,请重启 PostgreSQL 服务器。

  • pqxx::connection:表示到数据库的连接。它应该被创建一次,并且可以是长期存在的。
  • pqxx::work:一个事务对象。所有修改数据的 SQL 命令都应在一个事务中执行。它确保原子性(Atomicity):要么所有命令都成功,要么所有命令都不成功。
  • pqxx::nontransaction:一个非事务对象,通常用于只读的 SELECT 查询,以获得略微更好的性能。
  • 预处理语句 (Prepared Statements):执行相同查询多次但参数不同的最安全、最有效的方式。它们可以防止 SQL 注入攻击。

让我们创建一个 main.cpp 文件,演示完整的连接和 CRUD(创建、读取、更新、删除)生命周期。

#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;
}

在 main.cpp 所在的目录中创建一个 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 build
cd build
# 使用 CMake 配置项目
cMAKE ..
# 构建可执行文件
cMAKE --build .
# 运行应用程序
./app