19. DB 접근 Part 1: MySQL

아이펀 엔진의 ORM은 게임 오브젝트를 다루기 위한 DB 처리를 자동화 하지만, 경우에 따라 ORM 과 상관없이 DB 에 데이터를 쓰거나 읽는 등 DB 서버에 직접 접근해야될 수 있습니다. 이 경우 아래에 설명된 MySQL Connector 를 이용하여 MySQL 과 호환되는 모든 DB 에 SQL 처리를 할 수 있습니다.

Important

이 기능은 ORM 과 병행해서 사용하기 위한 용도가 아니라, 운영툴 등 별도의 table 을 접근해야되서 ORM 이 처리하지 못하는 부분을 다루기 위한 기능입니다. 만일 게임 오브젝트를 처리하기 위한 목적이라면 ORM 을 참고하세요.

ORM 이 다루는 오브젝트를 MySQL Connector 로 동시에 접근하는 것은 데이터의 유실 및 오동작의 원인이 될 수 있어 매우 위험합니다. 관련해서는 게임 서버 외부에서 DB 접근시 유의 사항 를 참조하시기 바랍니다.

Note

직접 MySQL client 라이브러리를 가져다 사용할 수도 있지만, 아이펀 엔진이 제공하는 클래스를 쓰시면 쓰레드 간섭에 대해서 신경쓰지 않고 처리할 수 있어서 더 편리합니다.

Tip

아래 설명들은 모든 기능을 설명하고 있지는 않으며 자세한 내용은 MariaDB class 를 참고 하시기 바랍니다.

19.1. DB 연결 초기화

19.1.1. 설정값 지정

Ptr<Mariadb> Mariadb::Create(host, user, password, schema,
                             num_connections [, charset] [, auto_retry_on_deadlock])
Mariadb Mariadb.Create(host, user, password, schema,
                       num_connections [, charset] [, auto_retry_on_deadlock])

DB 연결을 위한 설정값을 지정합니다.

host DB 서버의 주소를 입력합니다. 예) tcp://127.0.0.1:3306
user DB 계정의 ID 를 입력합니다.
password DB 계정의 Password 를 입력합니다.
schema DB 이름을 입력합니다.
num_connections Connection Pool 의 연결 수를 입력합니다.
charset Connection 의 Character Set 을 입력합니다. 생략하면 DB 기본 값을 사용합니다.
auto_retry_on_deadlock http://dev.mysql.com/doc/refman/5.5/en/innodb-deadlocks.html 설명을 참고하시기 바랍니다. 생략하면 false 입니다.

Note

Character Set 이 생성된 DB/Table/Column 과 다르면 문자열이 깨질 수 있습니다.

Note

Connection 수가 2 개 이상이면 병렬적으로 수행되며, 따라서 쿼리의 순서가 보장되지 않습니다.

Note

num_connections 가 클 경우 일부 연결이 실패하거나, 일정 시간 후 연결 끊김 로그가 출력될 수 있습니다. MySQL 설정에서 max_connections, backlog, max_allowed_packet 값을 늘리시기 바랍니다.

19.1.2. Thread pool 초기화

void Mariadb::Initialize()
void Mariadb.Start()

Create() 으로 지정된 연결 수만큼 connection pool 을 초기화 합니다. 이 함수가 불린 이후부터 query 를 실행할 수 있습니다.

19.2. 쿼리 실행

다음의 함수들 중 하나를 이용합니다. 끝에 Sync 가 붙은 함수는 동기방식으로 동작하는 함수입니다.

void Mariadb::ExecuteQuery(query, callback)
Ptr<ResultSets> Mariadb::ExecuteQuerySync(query, Mariadb::Error *error)
public void Mariadb.ExecuteQuery(query, callback)
public ResultSets ExecuteQuerySync(string query, out Error error)
query 실행하려는 SQL Query 문자열입니다.
callback 실행 결과를 처리하는 Callback 함수 입니다.
error 오류 발생시 오류 값을 전달하는 output 변수입니다.

Note

callback 은 void(const Ptr<ResultSets> &result_sets, const Mariadb::Error &error) 형태여야 합니다.

Note

현재 버전에서는 Prepared Statement 를 지원하지 않습니다.

19.3. MySQL connector 사용 예제

아래의 예제는 다음과 같은 테이블과 프로시져를 가정합니다.

Member table:

===================
No.  Name.  Weight.
-------------------
0    dog    12.5
1    cat    2.1
2    bird   0.5
3    tiger  290.0

ExampleTable table:

CREATE TABLE ExampleTable (date DATETIME)

ExampleProc procedure:

CREATE PROCEDURE ExampleProc ()
BEGIN
  SELECT * FROM Member;
END

ExampleProc2 procedure:

CREATE PROCEDURE ExampleProc2 (OUT param1 INT, OUT param2 CHAR(50))
BEGIN
  SET param1 = 1234;
  SET param2 = "test";
END

19.3.1. 예제 - SELECT

아래의 예제는 다음과 같은 결과를 출력합니다.

====================
No: 0, Name: dog, Weight: 12.5
No: 3, Name: tiger, Weight: 290
====================
No: 2, Name: bird, Weight: 0.5
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Ptr<Mariadb> connections;

void Initialize() {
  connections = Mariadb::Create("tcp://127.0.0.1:3306", "myid", "mypw",
                                "mydb", 5, "utf8", false);
  connections->Initialize();
}

void ExecuteQuery() {
  std::stringstream query;
  query << "SELECT * FROM Member WHERE Weight > 10.0;"
        << "SELECT * FROM Member WHERE Weight < 1.0;";

  connections->ExecuteQuery(query.str(), OnQueryExecuted);
}

void OnQueryExecuted(const Ptr<ResultSets> &result_sets,
                     const Mariadb::Error &error) {
  if (error.code != 0) {
    LOG(ERROR) << "Error: code=" << error.code << ", desc=" << error.desc;
    return;
  }

  // As many as SELECT queries.
  while (result_sets->SeekNextResultSet()) {
    LOG(INFO) << "=====================";

    // As many as result rows of each query.
    while (result_sets->SeekNextRow()) {
      LOG(INFO) << "No: " << result_sets->GetInt(0)
                << ", Name: " << result_sets->GetString(1)
                << ", Weight: " << result_sets->GetDouble(2);
    }
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Mariadb connections;

void Initialize() {
  Mariadb connections = Mariadb.Create (
      "tcp://127.0.0.1:3306", "funapi", "funapi", "funapi", 8, "utf8");

  connections.Start();
}

void ExecuteQuery() {
  StringBuilder strbuilder = new StringBuilder ();
  strbuilder.Append ("SELECT * FROM Member WHERE Weight > 10.0;");
  strbuilder.Append ("SELECT * FROM Member WHERE Weight < 1.0;");

  connections.ExecuteQuery (query.ToString(), OnQueryExecuted);
}

public void OnQueryExecuted(ResultSets result_sets, Mariadb.Error error)
{
  if (error.Code != 0)
  {
    Log.Info ("Error: code={0}, desc={1}", error.Code, error.Desc);
  }

  // As many as SELECT queries.
  while (result_sets.SeekNextResultSet ()) {
    Log.Info ("=====================");

    // As many as result rows of each query.
    while (result_sets.SeekNextRow ()) {
      Log.Info ("No: {0}", result_sets.GetString (0));  // 또는 GetInt("No")
      Log.Info (", Name: {0}", result_sets.GetString (1));
      Log.Info (", Weight: {0}", result_sets.GetDouble (2));
    }
  }
}

19.3.2. 예제 - INSERT

아래는 INSERT 후 SELECT 로 결과를 출력하는 예제입니다. 다음과 같은 결과를 출력합니다.

2016-06-14 16:23:19
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
Ptr<Mariadb> connections;

void Initialize() {
  connections = Mariadb::Create("tcp://127.0.0.1:3306", "myid", "mypw",
                                "mydb", 5, "utf8", false);
  connections->Initialize();
}

void ExecuteQuery() {
  string now = boost::posix_time::to_iso_extended_string(WallClock::Now());

  std::stringstream query;
  query << "INSERT INTO ExampleTable VALUES('" << now << "');";

  Mariadb::Error error;
  connections->ExecuteQuerySync(query.str(), &error);
  if (error.code != 0) {
    LOG(ERROR) << "Error: code=" << error.code << ", desc=" << error.desc;
    return;
  }

  string query2 = "SELECT date FROM ExampleTable;";
  connections->ExecuteQuery(query2, OnQueryExecuted);
}

void OnQueryExecuted(const Ptr<ResultSets> &result_sets,
                     const Mariadb::Error &error) {
  if (error.code != 0) {
    LOG(ERROR) << "Error: code=" << error.code << ", desc=" << error.desc;
    return;
  }
  if (not result_sets->SeekNextResultSet()) {
    LOG(ERROR) << "no result set.";
    return;
  }

  while (result_sets->SeekNextRow()) {
    WallClock::Value datetime = result_sets->GetDateTime(0);
    LOG(INFO) << "datetime: " << datetime;
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
Mariadb connections;

void Initialize() {
  Mariadb connections = Mariadb.Create (
      "tcp://127.0.0.1:3306", "funapi", "funapi", "funapi", 8, "utf8");

  connections.Start();
}

// 쿼리 실행
void ExecuteQuery()
{
  string query = "INSERT INTO ExampleTable VALUES('{0}');";
        string now = WallClock.GetTimestring(WallClock.Now);

  Mariadb.Error error;
  connections.ExecuteQuerySync (String.Format (query, now), out error);

  if (error.Code != 0)
  {
    Log.Info ("Error: code={0}, desc={1}", error.Code, error.Desc);
  }

  string query2 = "SELECT date FROM ExampleTable;";

  connections.ExecuteQuery(query2, OnQueryExecuted);
}

public void OnQueryExecuted(ResultSets result_sets, Mariadb.Error error)
{
  if (error.Code != 0)
  {
    Log.Info ("Error: code={0}, desc={1}", error.Code, error.Desc);
  }

  if (!result_sets.SeekNextResultSet ()) {
    Log.Error ("no result set.");
    return;
  }

  while (result_sets.SeekNextRow ()) {
    System.DateTime dateTime = result_sets.GetDateTime (0);
    Log.Info ("Datetime : {0}", dateTime);
  }
}

19.3.3. 예제 - Procedure 를 이용한 SELECT

아래는 다음과 같은 결과값을 생성하는, SELECT 를 직접 하는 대신 Procedure 를 호출해서 결과를 얻어내는 예제입니다.

No: 0, Name: dog, Weight: 12.5
No: 1, Name: cat, Weight: 2.1
No: 2, Name: bird, Weight: 0.5
No: 3, Name: tiger, Weight: 290
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Ptr<Mariadb> connections;

void Initialize() {
  connections = Mariadb::Create("tcp://127.0.0.1:3306", "myid", "mypw",
                                "mydb", 5, "utf8", false);
  connections->Initialize();
}

void ExecuteQuery() {
  std::stringstream query;
  query << "CALL ExampleProc();";

  connections->ExecuteQuery(query.str(), OnQueryExecuted);
}

void OnQueryExecuted(const Ptr<ResultSets> &result_sets,
                     const Mariadb::Error &error) {
  if (error.code != 0) {
    LOG(ERROR) << "Error: code=" << error.code << ", desc=" << error.desc;
    return;
  }

  if (not result_sets->SeekNextResultSet()) {
    LOG(ERROR) << "no result set.";
    return;
  }

  while (result_sets->SeekNextRow()) {
    LOG(INFO) << "No: " << result_sets->GetInt(0)
              << ", Name: " << result_sets->GetString(1)
              << ", Weight: " << result_sets->GetDouble(2);
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Mariadb connections;

void Initialize() {
  Mariadb connections = Mariadb.Create (
      "tcp://127.0.0.1:3306", "funapi", "funapi", "funapi", 8, "utf8");

  connections.Start();
}

public void ExecuteQuery(Mariadb connections)
{
  connections.ExecuteQuery ("CALL ExampleProc();", OnQueryExecuted);
}

public void OnQueryExecuted(ResultSets result_sets, Mariadb.Error error)
{
  if (error.Code != 0)
  {
    Log.Info ("Error: code={0}, desc={1}", error.Code, error.Desc);
  }

  while (result_sets.SeekNextResultSet ()) {
    while (result_sets.SeekNextRow ()) {
      Log.Info ("No: {0}", result_sets.GetInt (0));  // 또는 GetInt("No")
      Log.Info (", Name: {0}", result_sets.GetString (1));
      Log.Info (", Weight: {0}", result_sets.GetDoubl e(2));
    }
  }
}

19.3.4. 예제 - Procedure 의 OUTPUT 얻기

아래는 프로시져의 OUT 파라미터를 사용하는 예제입니다.

Note

현재 버전에서는 OUT 파라미터를 직접 받는 것은 지원하지 않습니다. SQL Session 변수에 대입하여 SELECT 합니다.

p1: 1234, p2: test
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Ptr<Mariadb> connections;

void Initialize() {
  connections = Mariadb::Create("tcp://127.0.0.1:3306", "myid", "mypw",
                                "mydb", 5, "utf8", false);
  connections->Initialize();
}

void ExecuteQuery() {
  std::stringstream query;
  query << "CALL ExampleProc2(@p1, @p2);"
        << "SELECT @p1, @p2;";

  connections->ExecuteQuery(query.str(), OnQueryExecuted);
}

void OnQueryExecuted(const Ptr<ResultSets> &result_sets,
                     const Mariadb::Error &error) {
  if (error.code != 0) {
    LOG(ERROR) << "Error: code=" << error.code << ", desc=" << error.desc;
    return;
  }

  if (not result_sets->SeekNextResultSet()) {
    LOG(ERROR) << "no result set.";
    return;
  }

  while (result_sets->SeekNextRow()) {
    LOG(INFO) << "p1: " << result_sets->GetInt(0)
              << ", p2: " << result_sets->GetString(1);
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Mariadb connections;

void Initialize() {
  Mariadb connections = Mariadb.Create (
      "tcp://127.0.0.1:3306", "funapi", "funapi", "funapi", 8, "utf8");

  connections.Start();
}

void ExecuteQuery()
{
  StringBuilder query = new StringBuilder ();
  query.Append ("CALL ExampleProc2(@p1, @p2);");
  query.Append ("SELECT @p1, @p2;");
  connections.ExecuteQuery (query.ToString(), OnQueryExecuted);
}

public void OnQueryExecuted(ResultSets result_sets, Mariadb.Error error)
{
  if (error.Code != 0)
  {
    Log.Info ("Error: code={0}, desc={1}", error.Code, error.Desc);
  }

  if (!result_sets.SeekNextResultSet ()) {
    Log.Error ("no result set.");
    return;
  }

  while (result_sets.SeekNextRow ()) {
    Log.Info ("P1 : {0}", result_sets.GetInt (0));
    Log.Info (", P2 : {0}", result_sets.GetString (1));
  }
}