C++

MySQL/MariaDB C/C++ 연동

멍텅구링 2024. 4. 25. 18:40

서버와 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 선언으로 설정해주면 된다.

 

lib 파일은 포함하지 않아도 된다..

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를 참고하면 된다.

 

참고

https://www.youtube.com/watch?v=WlEFQPvKUPo