서버와 DB를 연동하기 위해 MariaDB를 사용했는데, 생각보다 c++ 연동에 대한 정보가 없어 직접 DOCS를 보면서 사용법을 정리해보았다.
본 글은 Visual Studio 2022, MariaDB 11.3 version을 기준으로 작성되었다.
Visual Studio 설정
1. MySQL/MariaDB c/c++ connector를 다운
https://mariadb.com/docs/server/connect/programming-languages/cpp/
2. Visual Studio에서 헤더파일 설정
MySQL/MariaDB를 사용하고자 하는 프로젝트에서
속성 -> C/C++ -> 일반 -> 추가 포함 디렉터리
추가 포함 디렉터리에 설치한 c/c++ connector에 포함된 헤더파일 경로를 지정해준다.
MariaDB가 설치된 경로의 include/mysql 폴더에 위치해 있다.
사용하고자 하는 부분에 헤더파일 <mysql.h>를 추가해주고 라이브러리(libmariadb)에 대한 추가종속성을 추가해주면 된다.
libmariadb.lib 과 libmariadb.dll 파일을 추가해주어야 하는데, 간단하게 libmariadb.dll 파일은 프로젝트 폴더에, libmariad.lib 파일은 위에서 설정한 라이브러리 경로에 위치해 있으므로 #pragma 선언으로 설정해주면 된다.
3. Visual Studio에서 라이브러리 설정
속성 -> 링커 -> 일반 -> 추가 라이브러리 디렉터리
위와 똑같이 lib 폴더 위치를 지정해주면 된다.
MySQL/MariaDB 연결
기본적으로 사용되는 구조체는 다음과 같다.
MYSQL | DB에 대한 connection을 관리하는 객체. |
MYSQL_RES | tuple(row)를 반환하는 쿼리에 대한 결과를 담은 구조체. |
MYSQL_ROW | 하나의 tuple(row)을 나타내는 구조체. MYSQL_RES에서 mysql_fetch_row() 함수를 통해 접근. |
MYSQL_FIELD | attritube(field)에 대한 정보를 담은 구조체. MYSQL_RES에서 mysql_fetch_field() 함수를 통해 접근. |
전체적인 코드 흐름은
1. mysql_library_init() : MySQL client library를 초기화한다.
2. mysql_init() : DB 연결에 필요한 MySQL 객체를 할당.
3. mysql_real_connect() : 동기 방식으로 DB 서버에 연결.
4. mysql_query() : DB 서버에 쿼리를 보냄.
5. mysql_close() : DB 연결 종료.
6. mysql_library_end() : MySQL client library 종료.
7. mysql_error(), mysql_errno() : 에러 코드, 에러 메시지 호출
각 단계를 자세히 알아보면
1. mysql_library_init()
mysql client library를 초기화해주는 함수. non-multithreaded 환경이라면 생략해도 된다. 기본적으로 mysql_init()으로 대체가능하기 때문이다. 하지만 multithread환경에서는 mysql_library_init과 mysql_init 함수가 thread-safe 하지 않기 때문에 스레드를 이용하기 이전에 먼저 선언해주어야한다.
현 서버에서 지원해주는 방식은 mysql_library_init(0, NULL, NULL)로 선언하면 된다.
2. mysql_init(MYSQL *mysql)
mysql 객체가 NULL이면 함수는 새로운 MYSQL 객체를 반환한다.
메모리 누수를 막기 위해 mysql_init을 통해 할당한 MYSQL 객체는 mysql_close() 함수를 통해 메모리 해제 시켜주어야 한다.
3. mysql_real_connect(MYSQL *mysql, char * host, user, password, db, port, socket, flag)
동기방식으로 sql에 연결.
host 이름이 NULL 또는 localhost일 경우, 서버와 shared_memory를 통해 접근(윈도우 기준).
반대로 host 이름이 "."일 경우, named_pipe를 통해 통신. 따로 named-pipe 설정이 되어있지 않을 경우 에러발생한다.
그 외에는 TCP/IP를 통해 서버에 연결된다.
여기서는 로컬에서 연결할 것이기 때문에 host는 localhost를 이용하여 접근할 것이다
4. mysql_query(MYSQL* mysql)
쿼리를 실행하는 함수.
이때 SELECT 쿼리에 대한 결과 처리 방식이 두가지 인데, 결과에 대한 쿼리는 MYSQL_RES로 받아온다.
1) mysql_store_result() : 모든 결과 rows(tuple)을 받아오는 방식. 데이터를 한번에 받아오므로 out-of-memory가 될수도 있지만, result set(MYSQL_RES)에 대한 추가적인 처리를 할 수 있다(mysql_data_seek(), mysql_row_seek() 등).
2) mysql_use_result() : row를 연달아 받아오는 방식. 실제로 호출시에 메모리에 불러오는 방식이 아닌, mysql_fetch_row()이후에 불러온다. 메모리 부담이 적고, 속도적으로 더 빠르다. 하지만, 실제로 fetching중일때는 다른 클라이언트가 read lock에 의해 접근할 수 없기 때문에 다른 프로세스들이 동시에 접근하는 경우가 많을때는 사용하지 않는게 좋다.
코드
#include <iostream>
#include <mysql.h>
#include <string>
using namespaces std;
struct SQLConnection
{
string server, user, password, database;
SQLConnection(string server, string user, string password, string database) : server(server), user(user), password(password), database(database){ }
};
// connection을 통해 쿼리 실행
auto execSQLQuery(MYSQL* connection, string query)
{
struct result
{
bool success;
MYSQL_RES* res;
};
bool success = true;
if (mysql_query(connection, query.c_str()))
{
cout << "MySQL Query Error : " << mysql_error(connection) << endl;
success = false;
}
return result
{
success,
mysql_use_result(connection)
};
}
// db connection 생성
pair<bool, MYSQL*> sqlConnectionSetup(SQLConnection mysql_details)
{
// mysql_init(NULL) -> 새로운 db connection 생성
MYSQL* connection = mysql_init(NULL);
bool success = true;
if (!mysql_real_connect(connection, mysql_details.server.c_str(), mysql_details.user.c_str(), mysql_details.password.c_str(),
mysql_details.database.c_str(), 0, NULL, 0))
{
success = false;
cout << " Connection Error : " << mysql_error(connection) << endl;
exit(1);
}
return make_pair(success, connection);
}
int main()
{
mysql_library_init(0, NULL, NULL);
MYSQL* con = NULL;
MYSQL_ROW row;
SQLConnection sqlDetails("localhost", "root", "password", "dbname");
bool success;
tie(success, con) = sqlConnectionSetup(sqlDetails);
if (!success)
{
return -1;
}
// tablename 테이블에서 select 문 쿼리 발행
auto result = execSQLQuery(con, "select * from tablename;");
if (!result.success)
{
return 1;
}
cout << "Database Output : \n" << endl;
while ((row = mysql_fetch_row(result.res)) != NULL)
{
cout << "result fields num : " << mysql_num_fields(result.res) << endl;
cout << "result row num : " << mysql_num_rows(result.res) << endl;
cout << row[0] << " | " << row[1] << endl;
}
mysql_close(con);
mysql_library_end();
return 0;
}
정리
mysql 내에서 기본적으로 Prepared Statement와 비동기 방식도 지원해주고 있으므로 필요한 경우 DOCS를 참고하면 된다.
참고
'C++' 카테고리의 다른 글
std::vector의 복사 생성자(복사 대입) (6) | 2024.09.16 |
---|---|
Class, Struct, Union 메모리 구조 (1) | 2024.07.08 |
함수 호출 규약(function call convention) : _cdecl, _stdcall, _fastcall (1) | 2024.01.27 |