퀵스타트: C++와 ODBC로 연결 및 쿼리

적용 대상:SQL ServerAzure SQL Database

이 퀵스타트에서는 Windows, Linux, macOS에서 C++ 콘솔 애플리케이션을 구축합니다. 애플리케이션은 Microsoft ODBC Driver 18 for SQL Server를 사용하여 데이터베이스에 AdventureWorksLT 연결하고, 쿼리 매개변수를 바인딩하고, 쿼리를 실행하며, 결과 행을 읽습니다.

Prerequisites

  • Microsoft ODBC Driver 18 for SQL Server. Windows, Linux, macOS용 드라이버를 설치하세요.
  • C++17 컴파일러와 플랫폼 ODBC 개발 파일:
    • Windows에서는 Visual Studio 2022 또는 C++ 데스크톱 개발 워크로드와 함께 Visual Studio 2022 빌드 도구를 설치하세요. Windows SDK는 ODBC 헤더와 odbc32.lib를 제공합니다.
    • 리눅스에서는 C++ 컴파일러와 배포판용 unixODBC 개발 패키지를 설치하세요. 패키지는 ODBC 헤더와 libodbc를 제공합니다.
    • macOS에서는 Xcode 명령줄 도구와 Homebrew에서 유닉스ODBC를 설치하세요.
  • 샘플 데이터를 포함하는 AdventureWorksLT Azure SQL Database 또는 SQL Server 내 데이터베이스입니다. Azure SQL Database의 경우, 단일 데이터베이스를 생성할 때 샘플 데이터 소스를 선택하세요. SQL Server의 경우, AdventureWorks 샘플 데이터베이스에서 백업을 AdventureWorksLT 복원하세요.

운전자를 확인해

드라이버 관리자가 Microsoft ODBC Driver 18 for SQL Server를 찾을 수 있는지 확인하세요.

PowerShell에서 다음 명령을 실행하세요:

Get-OdbcDriver -Name "ODBC Driver 18 for SQL Server"

각 명령어는 ODBC Driver 18 for SQL Server를 나열해야 합니다. 드라이버가 목록에 없다면, 진행하기 전에 드라이버를 재설치하세요.

연결 설정

애플리케이션은 환경 변수에서 ODBC_CONNECTION_STRING 전체 연결 문자열을 읽습니다. 연결 문자열을 표시하지 않거나 명령줄 인수로 받아들이지 않습니다.

데이터베이스와 인증 방식에 맞는 Driver 18 연결 문자열을 사용하세요. 다음 SQL 인증 예시는 데이터베이스가 SQL 인증을 허용할 때 Windows, Linux, macOS에서 작동합니다:

Driver={ODBC Driver 18 for SQL Server};Server=tcp:<server>,1433;Database=<database>;UID=<user_id>;PWD=<password>;Encrypt=yes;TrustServerCertificate=no;

Microsoft Entra 인증 옵션에 대해서는 ODBC 드라이버와 함께 Microsoft Entra ID 사용하기를 참조하세요. 지원되는 모든 설정에 대해서는 DSN 및 연결 문자열 키워드와 속성을 참조하세요.

Microsoft ODBC 드라이버 18은 기본적으로 암호화를 활성화합니다. 애플리케이션의 요구사항이 명시적으로 되도록 명시 Encrypt=yes 하세요. 드라이버가 서버 인증서를 검증하도록 프로덕션 상태로 유지 TrustServerCertificate=no 하세요. 인증서는 서버 이름과 체인을 클라이언트가 신뢰하는 인증 기관과 일치해야 합니다. 구성 지침은 인증서 검증 실패를 참조하세요.

Caution

TrustServerCertificate=yes 인증서 검증을 건너뛰는 것. 클라이언트가 신뢰하는 인증서를 구성하는 동안 로컬 개발에만 사용하세요. 프로덕션 환경에서는 사용하지 마세요.

환경 변수는 연결 문자열을 셸 히스토리에 추가하지 않고 설정하세요.

VS 2022용 오픈 개발자 PowerShell. 이 명령어들을 실행한 후, 다음 프롬프트에 연결 문자열을 붙여넣으세요:

$secureConnectionString = Read-Host "ODBC connection string" -AsSecureString
$env:ODBC_CONNECTION_STRING = [System.Net.NetworkCredential]::new(
    "", $secureConnectionString).Password
$secureConnectionString = $null

환경 변수는 연결 문자열을 소스 파일에 포함시키지 않지만, 프로세스와 그 자식 프로세스는 이를 읽을 수 있습니다. 운영 애플리케이션의 경우, 가능한 경우 Microsoft Entra 인증을 사용하고 런타임에 보안 저장소에서 비밀을 가져오세요.

애플리케이션 만들기

  1. 프로젝트 디렉터리를 만들고 그 디렉터리에 변경하세요:

    New-Item -ItemType Directory odbc-quickstart
    Set-Location odbc-quickstart
    

  1. 다음 코드를 사용하여 odbc-quickstart.cpp라는 파일을 만듭니다.

    #ifdef _WIN32
    #include <windows.h>
    #endif
    
    #include <sql.h>
    #include <sqlext.h>
    #include <sqltypes.h>
    
    #include <cstdlib>
    #include <iomanip>
    #include <iostream>
    #include <string>
    
    std::string ReadEnvironmentVariable(const char* name)
    {
    #ifdef _WIN32
        char* value = nullptr;
        std::size_t length = 0;
        if (_dupenv_s(&value, &length, name) != 0 || value == nullptr)
            return {};
    
        std::string result(value);
        std::free(value);
        return result;
    #else
        const char* value = std::getenv(name);
        return value == nullptr ? std::string{} : value;
    #endif
    }
    
    void PrintDiagnostics(SQLSMALLINT handleType, SQLHANDLE handle)
    {
        SQLCHAR state[6];
        SQLINTEGER nativeError;
        SQLCHAR message[SQL_MAX_MESSAGE_LENGTH];
        SQLSMALLINT messageLength;
    
        for (SQLSMALLINT record = 1;
             SQL_SUCCEEDED(SQLGetDiagRec(handleType, handle, record, state,
                                         &nativeError, message, sizeof(message),
                                         &messageLength));
             ++record)
        {
            std::cerr << '[' << state << "] (" << nativeError << ") "
                      << message << '\n';
        }
    }
    
    bool Succeeded(SQLRETURN result, SQLSMALLINT handleType, SQLHANDLE handle)
    {
        if (SQL_SUCCEEDED(result))
            return true;
    
        PrintDiagnostics(handleType, handle);
        return false;
    }
    
    struct OdbcHandles
    {
        SQLHENV environment = SQL_NULL_HENV;
        SQLHDBC connection = SQL_NULL_HDBC;
        SQLHSTMT statement = SQL_NULL_HSTMT;
    
        ~OdbcHandles()
        {
            if (statement != SQL_NULL_HSTMT)
                SQLFreeHandle(SQL_HANDLE_STMT, statement);
            if (connection != SQL_NULL_HDBC)
            {
                SQLDisconnect(connection);
                SQLFreeHandle(SQL_HANDLE_DBC, connection);
            }
            if (environment != SQL_NULL_HENV)
                SQLFreeHandle(SQL_HANDLE_ENV, environment);
        }
    };
    
    int main()
    {
        std::string connectionString =
            ReadEnvironmentVariable("ODBC_CONNECTION_STRING");
        if (connectionString.empty())
        {
            std::cerr << "Set ODBC_CONNECTION_STRING before running.\n";
            return 1;
        }
    
        OdbcHandles handles;
        SQLRETURN result = SQLAllocHandle(
            SQL_HANDLE_ENV, SQL_NULL_HANDLE, &handles.environment);
        if (!SQL_SUCCEEDED(result))
        {
            std::cerr << "Unable to allocate an ODBC environment handle.\n";
            return 1;
        }
    
        result = SQLSetEnvAttr(
            handles.environment,
            SQL_ATTR_ODBC_VERSION,
            reinterpret_cast<SQLPOINTER>(SQL_OV_ODBC3_80),
            0);
        if (!Succeeded(result, SQL_HANDLE_ENV, handles.environment))
            return 1;
    
        result = SQLAllocHandle(
            SQL_HANDLE_DBC, handles.environment, &handles.connection);
        if (!Succeeded(result, SQL_HANDLE_ENV, handles.environment))
            return 1;
    
        result = SQLDriverConnect(
            handles.connection,
            nullptr,
            reinterpret_cast<SQLCHAR*>(connectionString.data()),
            SQL_NTS,
            nullptr,
            0,
            nullptr,
            SQL_DRIVER_NOPROMPT);
        if (!Succeeded(result, SQL_HANDLE_DBC, handles.connection))
            return 1;
    
        result = SQLAllocHandle(
            SQL_HANDLE_STMT, handles.connection, &handles.statement);
        if (!Succeeded(result, SQL_HANDLE_DBC, handles.connection))
            return 1;
    
        SQLINTEGER minimumProductId = 0;
        SQLLEN minimumProductIdLength = 0;
        result = SQLBindParameter(
            handles.statement,
            1,
            SQL_PARAM_INPUT,
            SQL_C_SLONG,
            SQL_INTEGER,
            10,
            0,
            &minimumProductId,
            0,
            &minimumProductIdLength);
        if (!Succeeded(result, SQL_HANDLE_STMT, handles.statement))
            return 1;
    
        SQLCHAR query[] =
            "SELECT TOP (5) ProductID, Name "
            "FROM SalesLT.Product "
            "WHERE ProductID > ? "
            "ORDER BY ProductID;";
        result = SQLExecDirect(handles.statement, query, SQL_NTS);
        if (!Succeeded(result, SQL_HANDLE_STMT, handles.statement))
            return 1;
    
        std::cout << "Product ID  Name\n"
                  << "----------  ----\n";
    
        while (SQL_SUCCEEDED(result = SQLFetch(handles.statement)))
        {
            SQLINTEGER productId;
            SQLLEN productIdLength;
            SQLCHAR productName[256];
            SQLLEN productNameLength;
    
            result = SQLGetData(
                handles.statement, 1, SQL_C_SLONG, &productId,
                sizeof(productId), &productIdLength);
            if (!Succeeded(result, SQL_HANDLE_STMT, handles.statement))
                return 1;
    
            result = SQLGetData(
                handles.statement, 2, SQL_C_CHAR, productName,
                sizeof(productName), &productNameLength);
            if (!Succeeded(result, SQL_HANDLE_STMT, handles.statement))
                return 1;
    
            std::cout << std::left << std::setw(12) << productId
                      << productName << '\n';
        }
    
        if (result != SQL_NO_DATA)
        {
            PrintDiagnostics(SQL_HANDLE_STMT, handles.statement);
            return 1;
        }
    
        return 0;
    }
    

애플리케이션은 표준 ODBC API만 사용하여 드라이버 관리자 헤더와 드라이버 관리자 라이브러리로의 링크를 포함합니다. 연결 문자열은 런타임에 Microsoft ODBC Driver 18 for SQL Server를 선택합니다.

SQL_DRIVER_NOPROMPT 구성 대화상자 열기를 막습니다 SQLDriverConnect . 연결 문자열이 불완전하면 호출은 오류를 반환하고 애플리케이션은 모든 진단 레코드를 출력합니다.

쿼리는 를 SQL Server 0 매개변수로 바인딩하고, SalesLT.Product의 처음 다섯 개 제품을 읽고, SQLGetData를 사용하여 각 제품 ID와 이름을 가져옵니다.

애플리케이션 빌드 및 실행

  1. 같은 개발자 PowerShell 창에서 애플리케이션을 컴파일하세요:

    cl /std:c++17 /EHsc /W4 odbc-quickstart.cpp /link odbc32.lib
    
  2. 애플리케이션을 실행합니다.

    .\odbc-quickstart.exe
    

애플리케이션의 표준 출력은 다음과 같습니다:

Product ID  Name
----------  ----
680         HL Road Frame - Black, 58
706         HL Road Frame - Red, 58
707         Sport-100 Helmet, Red
708         Sport-100 Helmet, Black
709         Mountain Bike Socks, M

끝나면 연결 문자열을 지우세요.

Remove-Item Env:\ODBC_CONNECTION_STRING