참고로 본 강의에서 모든 DB에 대한 내용을 다 다루지는 못한다.
대충 이렇게 한다는 흐름만 이해하고 필요한 부분이 있으면 찾아서 적용해야한다.
DB 연동
- DBConnectionPool
- DBConnection
두 개의 클래스를 이용해서 DB연결을 담당
#pragma once
#include "DBConnection.h"
class DBConnectionPool
{
public:
DBConnectionPool();
~DBConnectionPool();
bool Connect(int32 connectionCount, const WCHAR* connectionString);
void Clear();
DBConnection* Pop();
void Push(DBConnection* connection);
private:
USE_LOCK;
SQLHENV _environment = SQL_NULL_HANDLE;
Vector<DBConnection*> _connections;
// 사용변수를 보면 대충느낌이 오겠지만
// DBConnection를 여러개 만들어서 관리 하며
// DB의 환경정보 SQLHENV를 적용할 것이란것을 알 수 있다.
};
// DB와 연결을 담당
bool DBConnectionPool::Connect(
int32 connectionCount, // 몇개를 연결할 것인가
const WCHAR* connectionString) // 연결의 조건은
{
WRITE_LOCK;
// 핸들을 할당해 주세요
if (::SQLAllocHandle(SQL_HANDLE_ENV, // 핸들 할당
SQL_NULL_HANDLE,
&_environment) // 여기에 할당해 주세요
!= SQL_SUCCESS)
return false;
// 핸들의 Attr를 선언해주세요
// 여기서는 버전을 할당
if (::SQLSetEnvAttr(_environment, // 환경은
SQL_ATTR_ODBC_VERSION, // 버전은
reinterpret_cast<SQLPOINTER>(SQL_OV_ODBC3), // ODBC 3입니다
0) != SQL_SUCCESS)
return false;
// 몇개의 연결을 맺을 것인지
for (int32 i = 0; i < connectionCount; i++)
{
DBConnection* connection = xnew<DBConnection>();
// _environment, connectionString을 Connection 하면서 보냄을 기억하자
if (connection->Connect(_environment, connectionString) == false)
return false;
_connections.push_back(connection);
}
return true;
}
나머지 함수는 간단하기에 생략
#pragma once
#include <sql.h>
#include <sqlext.h>
class DBConnection
{
public:
bool Connect(SQLHENV henv, const WCHAR* connectionString);
void Clear();
bool Execute(const WCHAR* query); // 쿼리 명령실행
bool Fetch(); // 결과를 받는다
int32 GetRowCount(); // 행 개수
void Unbind();
public:
bool BindParam(SQLUSMALLINT paramIndex,
SQLSMALLINT cType,
SQLSMALLINT sqlType,
SQLULEN len,
SQLPOINTER ptr,
SQLLEN* index);
bool BindCol(SQLUSMALLINT columnIndex,
SQLSMALLINT cType,
SQLULEN len,
SQLPOINTER value,
SQLLEN* index);
void HandleError(SQLRETURN ret);
private:
SQLHDBC _connection = SQL_NULL_HANDLE; // DB핸들정보
SQLHSTMT _statement = SQL_NULL_HANDLE; // 상태정보
};
bool DBConnection::Connect(SQLHENV henv, const WCHAR* connectionString)
{
// DBC 핸들할당
if (::SQLAllocHandle(SQL_HANDLE_DBC, henv, &_connection) != SQL_SUCCESS)
return false;
WCHAR stringBuffer[MAX_PATH] = { 0 };
::wcscpy_s(stringBuffer, connectionString);
// 결과를 담는다
WCHAR resultString[MAX_PATH] = { 0 };
SQLSMALLINT resultStringLen = 0;
// SQLDriverConnectW를 이용해 DB를 Connect한다고 이해하자
SQLRETURN ret = ::SQLDriverConnectW(
_connection,
NULL,
reinterpret_cast<SQLWCHAR*>(stringBuffer),
_countof(stringBuffer),
OUT reinterpret_cast<SQLWCHAR*>(resultString),
_countof(resultString),
OUT & resultStringLen,
SQL_DRIVER_NOPROMPT
);
if (::SQLAllocHandle(SQL_HANDLE_STMT, _connection, &_statement) != SQL_SUCCESS)
return false;
return (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO);
}
// 쿼리 실행요청
bool DBConnection::Execute(const WCHAR* query)
{
SQLRETURN ret = ::SQLExecDirectW(_statement, (SQLWCHAR*)query, SQL_NTSL);
if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO)
return true;
HandleError(ret);
return false;
}
bool DBConnection::BindParam(SQLUSMALLINT paramIndex, SQLSMALLINT cType, SQLSMALLINT sqlType, SQLULEN len, SQLPOINTER ptr, SQLLEN* index)
{
// 몇 번째 인자를 뭘로 선언할 것인지 등을 선언
// 이후 실 사용예제로 보면 더 쉽다
SQLRETURN ret = ::SQLBindParameter(_statement,
paramIndex,
SQL_PARAM_INPUT,
cType,
sqlType,
len, 0, ptr, 0, index);
if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO)
{
HandleError(ret);
return false;
}
return true;
}
실제 사용은 이렇게 한다.
int main()
{
// DB Connect(이 부분은 어떻게 찾는지 아래서 다시 설명)
ASSERT_CRASH(GDBConnectionPool->Connect(1, L"Driver={SQL Server Native Client 11.0};Server=(localdb)\\MSSQLLocalDB;Database=ServerDb;Trusted_Connection=Yes;"));
// Create Table
{
// 테이블이 있으면 삭제 후 다시 생성
auto query = L" \
DROP TABLE IF EXISTS [dbo].[Gold]; \
CREATE TABLE [dbo].[Gold] \
( \
[id] INT NOT NULL PRIMARY KEY IDENTITY, \
[gold] INT NULL \
);";
// db connection을 하나 빼온다.
DBConnection* dbConn = GDBConnectionPool->Pop();
ASSERT_CRASH(dbConn->Execute(query));
GDBConnectionPool->Push(dbConn);
}
// Add Data
for (int32 i = 0; i < 3; i++)
{
DBConnection* dbConn = GDBConnectionPool->Pop();
// 기존에 바인딩 된 정보 날림
dbConn->Unbind();
// 넘길 인자 바인딩
int32 gold = 100;
SQLLEN len = 0;
// 넘길 인자 바인딩
ASSERT_CRASH(dbConn->BindParam(1, SQL_C_LONG, SQL_INTEGER, sizeof(gold), &gold, &len));
// SQL 실행
ASSERT_CRASH(dbConn->Execute(L"INSERT INTO [dbo].[Gold]([gold]) VALUES(?)"));
GDBConnectionPool->Push(dbConn);
}
// Read
{
DBConnection* dbConn = GDBConnectionPool->Pop();
// 기존에 바인딩 된 정보 날림
dbConn->Unbind();
int32 gold = 100;
SQLLEN len = 0;
// 넘길 인자 바인딩
ASSERT_CRASH(dbConn->BindParam(1, SQL_C_LONG, SQL_INTEGER, sizeof(gold), &gold, &len));
int32 outId = 0;
SQLLEN outIdLen = 0;
ASSERT_CRASH(dbConn->BindCol(1, SQL_C_LONG, sizeof(outId), &outId, &outIdLen));
int32 outGold = 0;
SQLLEN outGoldLen = 0;
ASSERT_CRASH(dbConn->BindCol(2, SQL_C_LONG, sizeof(outGold), &outGold, &outGoldLen));
// SQL 실행
ASSERT_CRASH(dbConn->Execute(L"SELECT id, gold FROM [dbo].[Gold] WHERE gold = (?)"));
while (dbConn->Fetch())
{
cout << "Id: " << outId << " Gold : " << outGold << endl;
}
GDBConnectionPool->Push(dbConn);
}
DB 세팅하기
목차 -> 보기 -> SQL Server 개체 탐색기
(만약에 뜨지않으면 Visual Studio Installer에서 DB관련 패키지를 설치해야함)
데이터베이스 우클릭 후 새 DB생성
생성한 Db에서 연결 문자열을 기록해 둔다
기록된 연결문자열을 아래에 사용한다.
int main()
{
// DB Connect
ASSERT_CRASH(GDBConnectionPool->Connect(1, L"Driver={SQL Server Native Client 11.0};Server=(localdb)\\ProjectsV13;Database=ServerDb;Trusted_Connection=Yes;"));
// Create Table
{
// ...