이 문서는 리눅스 환경에서 PostgreSQL 데이터베이스와 C++ 애플리케이션을 연동하는 방법을 안내합니다. 특히 libpqxx 라이브러리를 활용하여 데이터베이스를 설정하고, 기본적인 데이터 조작(CRUD) 및 스키마 관리(DDL) 작업을 수행하는 C++ 예제 코드를 제공합니다.
PostgreSQL 설치
먼저 리눅스 시스템에 PostgreSQL 데이터베이스 서버를 설치합니다. 주로 Debian/Ubuntu 계열에서는 apt 패키지 관리자를 사용합니다.
sudo apt-get update
sudo apt-get install postgresql
설치 완료 후, 다음 명령어로 PostgreSQL 서버 버전을 확인하여 정상적으로 설치되었는지 검증할 수 있습니다.
psql --version
C/C++ 드라이버 설치
C++ 애플리케이션에서 PostgreSQL과 통신하기 위해서는 클라이언트 라이브러리가 필요합니다. 여기서는 C 언어 바인딩인 libpq와 C++ 래퍼인 libpqxx를 설치합니다.
libpq 개발 라이브러리는 다음 명령어로 설치합니다:
sudo apt-get install libpq-dev
libpqxx는 소스 코드에서 컴파일하여 설치할 수 있습니다. 예를 들어, libpqxx-7.7.4.tar.gz 버전을 사용하는 경우 다음 단계를 따릅니다 (버전은 다를 수 있습니다):
wget https://github.com/libpqxx/libpqxx/releases/download/7.7.4/libpqxx-7.7.4.tar.gz
tar -zxvf libpqxx-7.7.4.tar.gz
cd libpqxx-7.7.4/
./configure --disable-documentation
make
sudo make install
--disable-documentation 옵션은 문서 생성을 건너뛰어 컴파일 시간을 단축합니다.
PostgreSQL 연동 C++ 예제
데이터베이스 연동을 위한 C++ 코드는 크게 헤더 파일, 소스 파일, 그리고 메인 실행 파일로 구성됩니다. 여기서는 데이터베이스 연결 및 모든 CRUD/DDL 작업을 관리하는 PgConnector 싱글톤 클래스를 구현합니다.
1. 헤더 파일 (PgConnector.h)
#ifndef PG_CONNECTOR_H
#define PG_CONNECTOR_H
#include <string>
#include <vector>
#include <map>
#include <memory> // For std::unique_ptr
#include <iostream> // For basic output
#include <pqxx/pqxx> // PostgreSQL C++ client library
// 컬럼 정의를 위한 구조체
struct ColumnDefinition {
std::string name;
std::string type;
bool isNotNull = false;
bool isPrimaryKey = false; // Note: For this example, primary key is 'id' BIGSERIAL.
};
// 데이터베이스 연결 및 작업을 관리하는 싱글톤 클래스
class PgConnector {
public:
// 싱글톤 인스턴스 접근 메서드
static PgConnector& getInstance();
// 스레드별 데이터베이스 연결 설정
bool establishThreadConnection(const std::string& dbName, const std::string& user, const std::string& password);
// 스레드별 데이터베이스 연결 해제
void closeThreadConnection();
// 데이터 삽입
bool insertData(const std::string& tableName, const std::map<std::string, std::string>& rowData);
// 데이터 갱신
bool updateData(const std::string& tableName, const std::string& conditionCol, const std::string& conditionVal,
const std::map<std::string, std::string>& newValues);
// 데이터 조회
std::vector<std::vector<std::string>> retrieveData(const std::string& tableName,
const std::vector<std::string>& columnsToSelect = {},
const std::string& conditionCol = "",
const std::string& conditionVal = "");
// 데이터 삭제
bool deleteData(const std::string& tableName, const std::string& conditionCol = "", const std::string& conditionVal = "");
// DDL 작업 - 테이블 추가
bool addTable(const std::string& tableName, const std::vector<ColumnDefinition>& columns);
// DDL 작업 - 테이블 삭제
bool dropTable(const std::string& tableName);
// DDL 작업 - 컬럼 추가
bool addColumnToTable(const std::string& tableName, const ColumnDefinition& column);
// DDL 작업 - 컬럼 삭제
bool removeColumnFromTable(const std::string& tableName, const std::string& columnName);
private:
// 싱글톤 패턴을 위한 비공개 생성자 및 복사/할당 방지
PgConnector();
PgConnector(const PgConnector&) = delete;
PgConnector& operator=(const PgConnector&) = delete;
~PgConnector();
// 초기 데이터베이스 설정 (데이터베이스 및 사용자 생성 등)
static bool setupInitialDatabase();
// 초기 테이블 생성 (사전 정의된 스키마에 따라)
static bool createDefinedTables();
// 데이터베이스/사용자 생성
bool createDbAndUser(const std::string& dbname, const std::string& user, const std::string& password);
// 싱글톤 인스턴스
static PgConnector* s_instance;
// DDL 작업에 사용될 관리자 연결 (주로 초기 설정에 사용)
static std::unique_ptr<pqxx::connection> s_adminConnection;
// 각 스레드별 데이터베이스 연결
static thread_local std::unique_ptr<pqxx::connection> s_threadConnection;
// 정의된 테이블 스키마 맵 (테이블 이름 -> 컬럼 정의 벡터)
static std::map<std::string, std::vector<ColumnDefinition>> s_tableSchemas;
// 관리자 연결 정보 (s_adminConnection용)
static std::string s_adminDbName;
static std::string s_adminUser;
static std::string s_adminPassword;
// 스키마 초기화 여부
static bool s_schemaInitialized;
};
#endif // PG_CONNECTOR_H
2. 소스 파일 (PgConnector.cpp)
#include "PgConnector.h"
#include <sstream> // For std::ostringstream
#include <algorithm> // For std::transform
PgConnector* PgConnector::s_instance = nullptr;
std::unique_ptr<pqxx::connection> PgConnector::s_adminConnection = nullptr;
thread_local std::unique_ptr<pqxx::connection> PgConnector::s_threadConnection = nullptr;
std::map<std::string, std::vector<ColumnDefinition>> PgConnector::s_tableSchemas;
std::string PgConnector::s_adminDbName;
std::string PgConnector::s_adminUser;
std::string PgConnector::s_adminPassword;
bool PgConnector::s_schemaInitialized = false;
PgConnector::PgConnector() {
// 기본 관리자 연결 정보 설정 (필요시 외부 설정으로 변경 가능)
s_adminDbName = "postgres"; // 시스템 데이터베이스
s_adminUser = "postgres";
s_adminPassword = "postgres"; // 주의: 실제 환경에서는 강력한 암호 사용
if (!s_schemaInitialized) {
setupInitialDatabase();
s_schemaInitialized = true;
}
}
PgConnector::~PgConnector() {
// s_adminConnection은 unique_ptr이 관리하므로 명시적 delete 불필요
// thread_local s_threadConnection도 스레드 종료 시 자동으로 소멸
std::cout << "PgConnector 인스턴스 소멸됨." << std::endl;
}
PgConnector& PgConnector::getInstance() {
if (!s_instance) {
s_instance = new PgConnector();
}
return *s_instance;
}
bool PgConnector::createDbAndUser(const std::string& dbname, const std::string& user, const std::string& password) {
if (!s_adminConnection || !s_adminConnection->is_open()) {
std::string connStr = "dbname=" + s_adminDbName + " user=" + s_adminUser + " password=" + s_adminPassword +
" hostaddr=127.0.0.1 port=5432";
try {
s_adminConnection.reset(new pqxx::connection(connStr));
if (!s_adminConnection->is_open()) {
std::cerr << "관리자 연결 실패: " << connStr << std::endl;
return false;
}
} catch (const pqxx::broken_connection& e) {
std::cerr << "관리자 연결 예외 (pqxx::broken_connection): " << e.what() << std::endl;
return false;
} catch (const std::exception& e) {
std::cerr << "관리자 연결 예외: " << e.what() << std::endl;
return false;
}
}
try {
pqxx::nontransaction txn(*s_adminConnection);
// 사용자 존재 여부 확인 및 생성
std::string checkUserSql = "SELECT 1 FROM pg_user WHERE usename = " + txn.quote(user);
if (txn.exec(checkUserSql).empty()) {
std::string createUserSql = "CREATE USER " + txn.quote_identifier(user) + " WITH PASSWORD " + txn.quote(password);
txn.exec(createUserSql);
std::cout << "사용자 '" << user << "' 생성 완료." << std::endl;
} else {
std::cout << "사용자 '" << user << "' 이미 존재." << std::endl;
}
// 데이터베이스 존재 여부 확인 및 생성
std::string checkDbSql = "SELECT 1 FROM pg_database WHERE datname = " + txn.quote(dbname);
if (txn.exec(checkDbSql).empty()) {
std::string createDbSql = "CREATE DATABASE " + txn.quote_identifier(dbname) + " WITH OWNER=" + txn.quote_identifier(user) + " ENCODING='UTF-8';";
txn.exec(createDbSql);
std::cout << "데이터베이스 '" << dbname << "' 생성 완료 (소유자: " << user << ")." << std::endl;
} else {
std::cout << "데이터베이스 '" << dbname << "' 이미 존재." << std::endl;
}
return true;
} catch (const std::exception& e) {
std::cerr << "데이터베이스/사용자 생성 중 예외 발생: " << e.what() << std::endl;
return false;
}
}
bool PgConnector::setupInitialDatabase() {
// 여기에서 실제 작업할 데이터베이스와 사용자를 정의합니다.
std::string targetDb = "measure_data_db";
std::string targetUser = "data_user";
std::string targetPass = "user_password";
if (!getInstance().createDbAndUser(targetDb, targetUser, targetPass)) {
return false;
}
// 초기 스키마 정의
s_tableSchemas.clear();
s_tableSchemas = {
{"frequency_spectrum", {
{"msg_id", "BIGINT", true},
{"serial_num", "BIGINT", true},
{"start_freq", "BIGINT"},
{"end_freq", "BIGINT"},
{"rbw_value", "DOUBLE PRECISION"},
{"data_type", "SMALLINT"},
{"data_count", "INTEGER"},
}},
{"time_domain_data", {
{"msg_id", "BIGINT", true},
{"serial_num", "BIGINT", true},
{"capture_time", "TIMESTAMP WITHOUT TIME ZONE"},
{"capture_time_ms", "TIMESTAMP WITHOUT TIME ZONE"},
{"raw_data", "INTEGER[]"},
}},
};
// DDL 작업은 해당 DB의 슈퍼유저나 소유자로 연결해서 수행하는 것이 일반적입니다.
// 여기서는 `s_adminConnection`을 `targetDb`로 잠시 재연결하는 방식을 사용하겠습니다.
s_adminConnection.reset(); // 기존 관리자 연결 해제
std::string targetConnStr = "dbname=" + targetDb + " user=" + targetUser + " password=" + targetPass +
" hostaddr=127.0.0.1 port=5432";
try {
s_adminConnection.reset(new pqxx::connection(targetConnStr));
if (!s_adminConnection->is_open()) {
std::cerr << "DDL 작업용 데이터베이스 '" << targetDb << "' 연결 실패." << std::endl;
return false;
}
std::cout << "DDL 작업을 위해 데이터베이스 '" << targetDb << "'에 연결되었습니다." << std::endl;
} catch (const std::exception& e) {
std::cerr << "DDL 작업용 데이터베이스 연결 중 예외 발생: " << e.what() << std::endl;
return false;
}
return createDefinedTables();
}
bool PgConnector::createDefinedTables() {
if (!s_adminConnection || !s_adminConnection->is_open()) {
std::cerr << "관리자 연결이 없으므로 초기 테이블을 생성할 수 없습니다." << std::endl;
return false;
}
try {
pqxx::work txn(*s_adminConnection);
for (const auto& entry : s_tableSchemas) {
const std::string& tableName = entry.first;
const std::vector<ColumnDefinition>& columns = entry.second;
std::string checkTableSql = "SELECT 1 FROM information_schema.tables WHERE table_name = " + txn.quote(tableName);
if (txn.exec(checkTableSql).empty()) {
std::ostringstream createTableSql;
createTableSql << "CREATE TABLE " << txn.quote_identifier(tableName) << " (";
createTableSql << txn.quote_identifier("id") << " BIGSERIAL PRIMARY KEY"; // 기본 ID 컬럼
for (const auto& col : columns) {
createTableSql << ", " << txn.quote_identifier(col.name) << " " << col.type;
if (col.isNotNull) {
createTableSql << " NOT NULL";
}
}
createTableSql << ");";
txn.exec(createTableSql.str());
std::cout << "테이블 '" << tableName << "' 생성 완료." << std::endl;
} else {
std::cout << "테이블 '" << tableName << "' 이미 존재." << std::endl;
}
}
txn.commit();
return true;
} catch (const std::exception& e) {
std::cerr << "초기 테이블 생성 중 예외 발생: " << e.what() << std::endl;
return false;
}
}
bool PgConnector::establishThreadConnection(const std::string& dbName, const std::string& user, const std::string& password) {
if (!s_threadConnection) {
std::string connStr = "dbname=" + dbName + " user=" + user + " password=" + password +
" hostaddr=127.0.0.1 port=5432";
try {
s_threadConnection.reset(new pqxx::connection(connStr));
if (s_threadConnection->is_open()) {
std::cout << "스레드별 연결 성공: dbname=" << dbName << ", user=" << user << std::endl;
return true;
} else {
std::cerr << "스레드별 연결 실패: dbname=" << dbName << ", user=" << user << std::endl;
return false;
}
} catch (const std::exception& e) {
std::cerr << "스레드별 연결 예외: " << e.what() << std::endl;
s_threadConnection.reset(); // 연결 실패 시 포인터 초기화
return false;
}
}
return s_threadConnection->is_open(); // 이미 연결되어 있으면 상태 반환
}
void PgConnector::closeThreadConnection() {
if (s_threadConnection) {
if (s_threadConnection->is_open()) {
s_threadConnection->disconnect();
std::cout << "스레드별 연결 해제됨." << std::endl;
}
s_threadConnection.reset();
}
}
bool PgConnector::insertData(const std::string& tableName, const std::map<std::string, std::string>& rowData) {
if (!s_threadConnection || !s_threadConnection->is_open()) {
std::cerr << "데이터 삽입 실패: 스레드별 연결이 열려있지 않습니다." << std::endl;
return false;
}
try {
pqxx::work txn(*s_threadConnection);
std::ostringstream sqlCols, sqlVals;
sqlCols << "INSERT INTO " << txn.quote_identifier(tableName) << " (";
sqlVals << "VALUES (";
bool first = true;
for (const auto& pair : rowData) {
if (!first) {
sqlCols << ", ";
sqlVals << ", ";
}
sqlCols << txn.quote_identifier(pair.first);
sqlVals << txn.quote(pair.second);
first = false;
}
sqlCols << ") ";
sqlVals << ");";
std::string fullSql = sqlCols.str() + sqlVals.str();
txn.exec(fullSql);
txn.commit();
return true;
} catch (const std::exception& e) {
std::cerr << "데이터 삽입 중 예외 발생: " << e.what() << std::endl;
return false;
}
}
bool PgConnector::updateData(const std::string& tableName, const std::string& conditionCol, const std::string& conditionVal,
const std::map<std::string, std::string>& newValues) {
if (!s_threadConnection || !s_threadConnection->is_open()) {
std::cerr << "데이터 갱신 실패: 스레드별 연결이 열려있지 않습니다." << std::endl;
return false;
}
try {
pqxx::work txn(*s_threadConnection);
std::ostringstream sql;
sql << "UPDATE " << txn.quote_identifier(tableName) << " SET ";
bool first = true;
for (const auto& pair : newValues) {
if (!first) {
sql << ", ";
}
sql << txn.quote_identifier(pair.first) << " = " << txn.quote(pair.second);
first = false;
}
sql << " WHERE " << txn.quote_identifier(conditionCol) << " = " << txn.quote(conditionVal) << ";";
txn.exec(sql.str());
txn.commit();
return true;
} catch (const std::exception& e) {
std::cerr << "데이터 갱신 중 예외 발생: " << e.what() << std::endl;
return false;
}
}
std::vector<std::vector<std::string>> PgConnector::retrieveData(const std::string& tableName,
const std::vector<std::string>& columnsToSelect,
const std::string& conditionCol,
const std::string& conditionVal) {
std::vector<std::vector<std::string>> results;
if (!s_threadConnection || !s_threadConnection->is_open()) {
std::cerr << "데이터 조회 실패: 스레드별 연결이 열려있지 않습니다." << std::endl;
return results;
}
try {
pqxx::nontransaction txn(*s_threadConnection);
std::ostringstream sql;
sql << "SELECT ";
if (columnsToSelect.empty()) {
sql << "*";
} else {
bool first = true;
for (const std::string& col : columnsToSelect) {
if (!first) sql << ", ";
sql << txn.quote_identifier(col);
first = false;
}
}
sql << " FROM " << txn.quote_identifier(tableName);
if (!conditionCol.empty() && !conditionVal.empty()) {
sql << " WHERE " << txn.quote_identifier(conditionCol) << " = " << txn.quote(conditionVal);
}
sql << ";";
pqxx::result res = txn.exec(sql.str());
for (const auto& row : res) {
std::vector<std::string> record;
for (const auto& field : row) {
record.push_back(field.c_str());
}
results.push_back(record);
}
return results;
} catch (const std::exception& e) {
std::cerr << "데이터 조회 중 예외 발생: " << e.what() << std::endl;
return results;
}
}
bool PgConnector::deleteData(const std::string& tableName, const std::string& conditionCol, const std::string& conditionVal) {
if (!s_threadConnection || !s_threadConnection->is_open()) {
std::cerr << "데이터 삭제 실패: 스레드별 연결이 열려있지 않습니다." << std::endl;
return false;
}
try {
pqxx::work txn(*s_threadConnection);
std::ostringstream sql;
sql << "DELETE FROM " << txn.quote_identifier(tableName);
if (!conditionCol.empty() && !conditionVal.empty()) {
sql << " WHERE " << txn.quote_identifier(conditionCol) << " = " << txn.quote(conditionVal);
}
sql << ";";
txn.exec(sql.str());
txn.commit();
return true;
} catch (const std::exception& e) {
std::cerr << "데이터 삭제 중 예외 발생: " << e.what() << std::endl;
return false;
}
}
bool PgConnector::addTable(const std::string& tableName, const std::vector<ColumnDefinition>& columns) {
if (!s_adminConnection || !s_adminConnection->is_open()) {
std::cerr << "테이블 추가 실패: 관리자 연결이 열려있지 않습니다." << std::endl;
return false;
}
try {
pqxx::work txn(*s_adminConnection);
std::string checkTableSql = "SELECT 1 FROM information_schema.tables WHERE table_name = " + txn.quote(tableName);
if (!txn.exec(checkTableSql).empty()) {
std::cout << "테이블 '" << tableName << "' 이미 존재합니다. 추가 작업을 건너뜀." << std::endl;
return false;
}
std::ostringstream createTableSql;
createTableSql << "CREATE TABLE " << txn.quote_identifier(tableName) << " (";
createTableSql << txn.quote_identifier("id") << " BIGSERIAL PRIMARY KEY";
for (const auto& col : columns) {
createTableSql << ", " << txn.quote_identifier(col.name) << " " << col.type;
if (col.isNotNull) {
createTableSql << " NOT NULL";
}
}
createTableSql << ");";
txn.exec(createTableSql.str());
txn.commit();
s_tableSchemas[tableName] = columns; // 스키마 맵 업데이트
std::cout << "테이블 '" << tableName << "'이(가) 성공적으로 추가되었습니다." << std::endl;
return true;
} catch (const std::exception& e) {
std::cerr << "테이블 추가 중 예외 발생: " << e.what() << std::endl;
return false;
}
}
bool PgConnector::dropTable(const std::string& tableName) {
if (!s_adminConnection || !s_adminConnection->is_open()) {
std::cerr << "테이블 삭제 실패: 관리자 연결이 열려있지 않습니다." << std::endl;
return false;
}
try {
pqxx::work txn(*s_adminConnection);
std::string sql = "DROP TABLE IF EXISTS " + txn.quote_identifier(tableName) + ";";
txn.exec(sql);
txn.commit();
s_tableSchemas.erase(tableName); // 스키마 맵에서 제거
std::cout << "테이블 '" << tableName << "'이(가) 성공적으로 삭제되었습니다." << std::endl;
return true;
} catch (const std::exception& e) {
std::cerr << "테이블 삭제 중 예외 발생: " << e.what() << std::endl;
return false;
}
}
bool PgConnector::addColumnToTable(const std::string& tableName, const ColumnDefinition& column) {
if (!s_adminConnection || !s_adminConnection->is_open()) {
std::cerr << "컬럼 추가 실패: 관리자 연결이 열려있지 않습니다." << std::endl;
return false;
}
try {
pqxx::work txn(*s_adminConnection);
std::string checkColumnSql = "SELECT 1 FROM information_schema.columns WHERE table_name = " + txn.quote(tableName) +
" AND column_name = " + txn.quote(column.name);
if (!txn.exec(checkColumnSql).empty()) {
std::cout << "테이블 '" << tableName << "'에 컬럼 '" << column.name << "'이(가) 이미 존재합니다." << std::endl;
return false;
}
std::ostringstream sql;
sql << "ALTER TABLE " << txn.quote_identifier(tableName) << " ADD COLUMN "
<< txn.quote_identifier(column.name) << " " << column.type;
if (column.isNotNull) {
sql << " NOT NULL";
}
sql << ";";
txn.exec(sql.str());
txn.commit();
std::cout << "테이블 '" << tableName << "'에 컬럼 '" << column.name << "'이(가) 성공적으로 추가되었습니다." << std::endl;
// s_tableSchemas 업데이트 로직은 필요에 따라 추가
return true;
} catch (const std::exception& e) {
std::cerr << "컬럼 추가 중 예외 발생: " << e.what() << std::endl;
return false;
}
}
bool PgConnector::removeColumnFromTable(const std::string& tableName, const std::string& columnName) {
if (!s_adminConnection || !s_adminConnection->is_open()) {
std::cerr << "컬럼 삭제 실패: 관리자 연결이 열려있지 않습니다." << std::endl;
return false;
}
try {
pqxx::work txn(*s_adminConnection);
std::string checkColumnSql = "SELECT 1 FROM information_schema.columns WHERE table_name = " + txn.quote(tableName) +
" AND column_name = " + txn.quote(columnName);
if (txn.exec(checkColumnSql).empty()) {
std::cout << "테이블 '" << tableName << "'에 컬럼 '" << columnName << "'이(가) 존재하지 않습니다." << std::endl;
return false;
}
std::ostringstream sql;
sql << "ALTER TABLE " << txn.quote_identifier(tableName) << " DROP COLUMN "
<< txn.quote_identifier(columnName) << ";";
txn.exec(sql.str());
txn.commit();
std::cout << "테이블 '" << tableName << "'에서 컬럼 '" << columnName << "'이(가) 성공적으로 제거되었습니다." << std::endl;
// s_tableSchemas 업데이트 로직은 필요에 따라 추가
return true;
} catch (const std::exception& e) {
std::cerr << "컬럼 삭제 중 예외 발생: " << e.what() << std::endl;
return false;
}
}
3. 메인 파일 (main.cpp)
#include <iostream>
#include <vector>
#include <string>
#include <thread>
#include <map>
#include <chrono>
#include "PgConnector.h"
// 스레드별 데이터 삽입 함수
void performDataInsertion(int thread_id, const std::string& dbName, const std::string& user, const std::string& password) {
PgConnector& connector = PgConnector::getInstance();
if (!connector.establishThreadConnection(dbName, user, password)) {
std::cerr << "스레드 " << thread_id << " - 데이터베이스 연결 실패." << std::endl;
return;
}
std::string tableName = "frequency_spectrum";
for (int i = 0; i < 50; ++i) { // 각 스레드에서 50개 데이터 삽입
std::map<std::string, std::string> data;
data["msg_id"] = std::to_string(1000 + thread_id);
data["serial_num"] = std::to_string(20000 + i);
data["start_freq"] = std::to_string(1000000 + i * 100);
data["end_freq"] = std::to_string(2000000 + i * 100);
data["rbw_value"] = "0.5";
data["data_type"] = "1";
data["data_count"] = "500";
if (!connector.insertData(tableName, data)) {
std::cerr << "스레드 " << thread_id << " - 데이터 삽입 실패: msg_id=" << data["msg_id"] << std::endl;
} else {
//std::cout << "스레드 " << thread_id << " - 데이터 삽입 성공: msg_id=" << data["msg_id"] << std::endl;
}
}
connector.closeThreadConnection();
}
// 스레드별 데이터 삭제 함수
void performDataDeletion(int thread_id, const std::string& dbName, const std::string& user, const std::string& password, const std::string& target_msg_id) {
PgConnector& connector = PgConnector::getInstance();
if (!connector.establishThreadConnection(dbName, user, password)) {
std::cerr << "스레드 " << thread_id << " - 데이터베이스 연결 실패." << std::endl;
return;
}
std::string tableName = "frequency_spectrum";
if (connector.deleteData(tableName, "msg_id", target_msg_id)) {
std::cout << "스레드 " << thread_id << " - msg_id=" << target_msg_id << " 데이터 삭제 성공." << std::endl;
} else {
std::cerr << "스레드 " << thread_id << " - msg_id=" << target_msg_id << " 데이터 삭제 실패." << std::endl;
}
connector.closeThreadConnection();
}
void performDataQuery(const std::string& dbName, const std::string& user, const std::string& password) {
PgConnector& connector = PgConnector::getInstance();
if (!connector.establishThreadConnection(dbName, user, password)) {
std::cerr << "메인 스레드 - 데이터베이스 연결 실패." << std::endl;
return;
}
std::string tableName = "frequency_spectrum";
std::cout << "\n모든 데이터 조회 (frequency_spectrum):" << std::endl;
std::vector<std::vector<std::string>> all_data = connector.retrieveData(tableName);
for (const auto& row : all_data) {
for (const auto& field : row) {
std::cout << field << "\t";
}
std::cout << std::endl;
}
std::cout << "\nmsg_id='1001' 데이터 조회 (frequency_spectrum):" << std::endl;
std::vector<std::vector<std::string>> specific_data = connector.retrieveData(tableName, {}, "msg_id", "1001");
for (const auto& row : specific_data) {
for (const auto& field : row) {
std::cout << field << "\t";
}
std::cout << std::endl;
}
connector.closeThreadConnection();
}
int main() {
// PgConnector 인스턴스 초기화 및 DB/테이블 생성 (싱글톤 생성자에서 처리됨)
PgConnector& connectorInstance = PgConnector::getInstance();
std::string dbName = "measure_data_db";
std::string user = "data_user";
std::string password = "user_password";
// 여러 스레드를 이용한 데이터 삽입
std::cout << "--- 다중 스레드 데이터 삽입 시작 ---" << std::endl;
std::vector<std::thread> insertion_threads;
for (int i = 0; i < 3; ++i) {
insertion_threads.emplace_back(performDataInsertion, i, dbName, user, password);
}
for (auto& t : insertion_threads) {
t.join();
}
std::cout << "--- 다중 스레드 데이터 삽입 완료 ---" << std::endl;
// 데이터 조회 예시
performDataQuery(dbName, user, password);
// 데이터 갱신 예시
std::cout << "\n--- 데이터 갱신 시작 ---" << std::endl;
std::string updateTableName = "frequency_spectrum";
std::map<std::string, std::string> updates = {
{"rbw_value", "1.0"},
{"data_count", "1000"}
};
if (connectorInstance.establishThreadConnection(dbName, user, password)) {
if (connectorInstance.updateData(updateTableName, "msg_id", "1000", updates)) {
std::cout << "msg_id '1000' 데이터 갱신 성공." << std::endl;
} else {
std::cerr << "msg_id '1000' 데이터 갱신 실패." << std::endl;
}
connectorInstance.closeThreadConnection();
} else {
std::cerr << "갱신을 위한 연결 실패." << std::endl;
}
std::cout << "--- 데이터 갱신 완료 ---" << std::endl;
// 갱신된 데이터 확인
performDataQuery(dbName, user, password);
// 데이터 삭제 예시 (단일 스레드 또는 다중 스레드)
std::cout << "\n--- 데이터 삭제 시작 ---" << std::endl;
std::vector<std::thread> deletion_threads;
deletion_threads.emplace_back(performDataDeletion, 5, dbName, user, password, "1001");
deletion_threads.emplace_back(performDataDeletion, 6, dbName, user, password, "1002");
for (auto& t : deletion_threads) {
t.join();
}
std::cout << "--- 데이터 삭제 완료 ---" << std::endl;
// 삭제 후 데이터 확인
performDataQuery(dbName, user, password);
// 추가 테이블 생성 예시
std::cout << "\n--- 새 테이블 추가 시작 ---" << std::endl;
std::vector<ColumnDefinition> newTableCols = {
{"sensor_id", "INTEGER", true},
{"location", "VARCHAR(255)"},
{"temperature", "NUMERIC(5,2)"}
};
connectorInstance.addTable("sensor_readings", newTableCols);
std::cout << "--- 새 테이블 추가 완료 ---" << std::endl;
// 새 컬럼 추가 예시
std::cout << "\n--- 컬럼 추가 시작 ---" << std::endl;
ColumnDefinition newCol = {"humidity", "NUMERIC(5,2)"};
connectorInstance.addColumnToTable("sensor_readings", newCol);
std::cout << "--- 컬럼 추가 완료 ---" << std::endl;
// 컬럼 삭제 예시
std::cout << "\n--- 컬럼 삭제 시작 ---" << std::endl;
connectorInstance.removeColumnFromTable("sensor_readings", "humidity");
std::cout << "--- 컬럼 삭제 완료 ---" << std::endl;
// 테이블 삭제 예시
std::cout << "\n--- 테이블 삭제 시작 ---" << std::endl;
connectorInstance.dropTable("sensor_readings");
std::cout << "--- 테이블 삭제 완료 ---" << std::endl;
return 0;
}
4. 컴파일
위 코드들을 main.cpp와 PgConnector.cpp로 각각 저장한 후, 다음 명령어를 사용하여 컴파일합니다:
g++ -pthread --std=c++17 -o pg_client_demo main.cpp PgConnector.cpp -lpqxx -lpq
이 명령어는 -pthread 옵션으로 스레드 지원을 활성화하고, --std=c++17로 C++17 표준을 사용합니다. -lpqxx와 -lpq는 각각 libpqxx와 libpq 라이브러리에 연결하는 옵션입니다.