MySqlConnector 패키지를 이용하여 DB와 통신할 수 있습니다. static 메서드를 통해 별도의 인스턴스 없이 편리하게 접근하여 DB 쿼리를 작성할 수 있도록 구현합니다.

📺 미리 보기

 

💬 서론

  • '미리보기'에 나오는 영상은 Morph! 게임에서 사용자의 로그인을 하는 과정입니다.
  • 작성한 'MySqlManager' 클래스는 서버에 구현되어있으며, 클라이언트는 서버를 통해 DB에 접근하는 방식입니다.
  • 이렇게 구현한 이유는 MySQL에 접근하기위한 접속쿼리가 클라이언트 코드에 노출되지 않게 하기 위함입니다.
    • 클라이언트의 디컴파일을 통해 리터럴이 노출되면 DB 자체가 공격을 받을 수 있기 때문입니다.

 

📖 구현 내용

  • MySqlManager에서는 Insert, Update 등을 수행할 수 있는 'Command' 메서드와 데이터를 읽기 위한 'Select' 메서드를 구현하였습니다.

 

⚒️ 구현

· 구현 내용

using MySqlConnector;
using Server.Game;
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

/// <summary>
/// MySQL 쿼리 매니저
/// </summary>
public class MySqlManager
{
    private static ThreadLocal<MySqlConnection> _Connection = new ThreadLocal<MySqlConnection>(); // SQL Connection

    private static string _SqlConnection = null; //SQL 접속 명령인자

    static MySqlManager()
    {
        // 접속 명령인자 생성
        StringBuilder builder = new StringBuilder();

        builder.AppendFormat("Server={0};", "192.168.0.1"); // DB가 설치된 ID
        builder.AppendFormat("Port={0};", "1234"); // DB의 포트 번호
        builder.AppendFormat("Database={0};", "sampleDB"); // 데이터베이스 이름
        builder.AppendFormat("Uid={0};", "sampleUser"); // 사용자 이름
        builder.AppendFormat("Pwd={0}", "samplePassword"); // 사용자의 비밀번호

        _SqlConnection = builder.ToString();
    }

    private static void MySqlConnect()
    {
        try
        {
            _Connection.Value = new MySqlConnection(_SqlConnection);
            _Connection.Value.Open();

            if (_Connection.Value.State != ConnectionState.Open)
            {
                throw new InvalidOperationException("Connection failed to open.");
            }
        }
        catch (Exception ex)
        {
            Logger.Log(ex.Message);
        }
    }

    private static void MySqlDisconnect()
    {
        if (_Connection.Value != null && _Connection.Value.State != ConnectionState.Closed)
        {
            _Connection.Value.Close();
            _Connection.Value = null;
        }
    }

    public static MySqlErrorCode Command(string command)
    {
        Logger.Log($"[SELECT]: {command}");

        MySqlConnect(); // 접속

        try
        {
            MySqlCommand dbcmd = new MySqlCommand(command, _Connection.Value); // 명령어를 커맨드에 입력
            dbcmd.ExecuteNonQuery(); // 명령어를 SQL에 보냄

            return MySqlErrorCode.None;
        }
        catch (MySqlException e) // SQL 오류
        {
            Logger.Log(e.ToString());

            return e.ErrorCode;
        }
        finally
        {
            MySqlDisconnect(); // 접속 해제
        }
    }

    public static DataTableReader Select(string command)
    {
        Logger.Log($"[SELECT]: {command}");

        MySqlConnect(); // 접속

        try
        {
            MySqlDataAdapter adapter = new MySqlDataAdapter(command, _Connection.Value);
            DataTable table = new DataTable(); // 테이블 생성
            adapter.Fill(table); // 데이터 테이블 채우기

            return table.CreateDataReader(); // 성공적으로 select를 했다면, 데이터 리더를 생성
        }
        catch (MySqlException e) // SQL 오류 발생
        {
            Logger.Log(e.ToString());

            return null;
        }
        finally
        {
            MySqlDisconnect(); // 접속 해제
        }
    }
}

 

builder.AppendFormat("Server={0};", "192.168.0.1"); // DB가 설치된 ID
builder.AppendFormat("Port={0};", "1234"); // DB의 포트 번호
builder.AppendFormat("Database={0};", "sampleDB"); // 데이터베이스 이름
builder.AppendFormat("Uid={0};", "sampleUser"); // 사용자 이름
builder.AppendFormat("Pwd={0}", "samplePassword"); // 사용자의 비밀번호
  • 이곳에서 접속할 데이터베이스의 접속인자를 정의합니다. 이 코드에서는 임시로 채워넣었습니다.

 

public static MySqlErrorCode Command(string command)
  • Command 메서드는 테이블을 읽지 않는 쿼리를 요청할때 사용할 수 있습니다.
  • MySqlErrorCode를 리턴하여 성공적으로 Command를 수행했는지 확인할 수 있습니다.

 

public static DataTableReader Select(string command)
  • Select 메서드는 테이블을 읽고, 이를 리턴하기위해 사용할 수 있습니다.
  • 사용자의 로그인, 회원가입 읽기 등 다양한 부분에서 사용합니다.

 

✅ 적용

· Command

public static void AddCoin(int playerUniqueId, int amount)
{
    DataTableReader balanceInfo = MySqlManager.Select($"select coins from balance where user_id = {playerUniqueId}");
    if (balanceInfo.Read())
    {
        MySqlManager.Command($"update balance set coins = {balanceInfo.GetInt32(0) + amount} where user_id = {playerUniqueId};");
    }
    else
    {
        Logger.Log($"[ERROR EXCEPTION] No {playerUniqueId} Level Column from DB!");
    }
}
  • 다음과 같이 Command를 사용할 수 있습니다.
  • Command는 UPDATE문 등 테이블을 읽지 않는 쿼리를 사용하기 위한 메서드입니다.

 

· Select

/// <summary>
/// 플레이어의 현재 인벤토리 정보를 전송
/// </summary>
/// <param name="clientSession"></param>
public static void SendPlayerInventory(ClientSession clientSession)
{
    int PlayerUniqueId = clientSession.MyPlayer.PlayerInfo.PlayerUniqueId;
    S_RespondPlayerGameData s_RespondPlayerGameData = new S_RespondPlayerGameData();

    // 인벤토리 정보 가져오기
    DataTableReader inventoryInfo = MySqlManager.Select($"select item_id, amount from inventory where user_id = {PlayerUniqueId}");
    while (inventoryInfo.Read())
    {
        int item_id = Convert.ToInt32(inventoryInfo["item_id"]); // item_id 열 값 가져오기
        int amount = Convert.ToInt32(inventoryInfo["amount"]);   // amount 열 값 가져오기

        // DbItemData 객체 생성 및 값 할당
        DbItemData itemData = new DbItemData()
        {
            EntityCode = item_id,
            Count = amount,
        };

        // DbItemDatas 추가
        s_RespondPlayerGameData.InventoryInfo.Add(itemData);
    }

    DataTableReader expInfo = MySqlManager.Select($"select exp, lv from level where user_id = {PlayerUniqueId}");
    if (expInfo.Read())
    {
        s_RespondPlayerGameData.Exp = expInfo.GetInt32(0);
        s_RespondPlayerGameData.Level = expInfo.GetInt32(1);
    }
    else
        Logger.Log($"uniqueId {PlayerUniqueId} is null from DB!");

    DataTableReader coinInfo = MySqlManager.Select($"select coins, skill_point from balance where user_id = {PlayerUniqueId}");
    if (coinInfo.Read())
    {
        s_RespondPlayerGameData.Coins = coinInfo.GetInt32(0);
        s_RespondPlayerGameData.SkillPoint = coinInfo.GetInt32(1);
    }
    else
        Logger.Log($"uniqueId {PlayerUniqueId} is null from DB!");

    // 데이터 전송
    clientSession.Send(s_RespondPlayerGameData);

    // 할당 인벤토리 데이터 전송
    SendAllocatedInventorySelf(clientSession);
}
  • 위 코드는 플레이어의 인벤토리 정보를 전달하기 위한 메서드입니다.
  • Select 쿼리를 사용하여 DB에서 정보를 읽고, 이를 추출하여 패킷에 담아 보냅니다.

 

🕹️ Unity Affiliate

  • Unity Affiliate Program 파트너로서 아래의 배너를 통해 접속하신 경우 수수료를 받을 수 있습니다.
  • 아래 배너의 에셋들은 '실시간 무료 에셋 랭킹'을 나타냅니다.
bonnate