From 34394f2c96b2f07ba585de87d707cf46521c9eaa Mon Sep 17 00:00:00 2001 From: qornwh1 Date: Sun, 1 Mar 2026 16:58:38 +0900 Subject: [PATCH] =?UTF-8?q?feat=20:=20RDB=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC,=20b?= =?UTF-8?q?ase=20=EC=BD=94=EB=93=9C=20=EA=B5=AC=ED=98=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MMOTestServer/MMOserver/MMOserver.csproj | 9 ++ MMOTestServer/MMOserver/Program.cs | 11 +++ .../MMOserver/RDB/DbConnectionFactory.cs | 31 +++++++ .../MMOserver/RDB/Handlers/TestHandlers.cs | 48 ++++++++++ MMOTestServer/MMOserver/RDB/Models/TestM.cs | 16 ++++ .../RDB/Repositories/TestRepository.cs | 18 ++++ .../MMOserver/RDB/Services/TestService.cs | 57 ++++++++++++ MMOTestServer/MMOserver/config.json | 15 ++++ .../RDB/Database/DbConnectionFactory.cs | 8 ++ .../ServerLib/RDB/Handlers/HelperHandler.cs | 22 +++++ .../ServerLib/RDB/Handlers/Response.cs | 8 ++ .../ServerLib/RDB/Repositories/ARepository.cs | 90 +++++++++++++++++++ MMOTestServer/ServerLib/ServerLib.csproj | 4 + ReadMe.md | 73 +++++++++++++++ 14 files changed, 410 insertions(+) create mode 100644 MMOTestServer/MMOserver/RDB/DbConnectionFactory.cs create mode 100644 MMOTestServer/MMOserver/RDB/Handlers/TestHandlers.cs create mode 100644 MMOTestServer/MMOserver/RDB/Models/TestM.cs create mode 100644 MMOTestServer/MMOserver/RDB/Repositories/TestRepository.cs create mode 100644 MMOTestServer/MMOserver/RDB/Services/TestService.cs create mode 100644 MMOTestServer/MMOserver/config.json create mode 100644 MMOTestServer/ServerLib/RDB/Database/DbConnectionFactory.cs create mode 100644 MMOTestServer/ServerLib/RDB/Handlers/HelperHandler.cs create mode 100644 MMOTestServer/ServerLib/RDB/Handlers/Response.cs create mode 100644 MMOTestServer/ServerLib/RDB/Repositories/ARepository.cs create mode 100644 ReadMe.md diff --git a/MMOTestServer/MMOserver/MMOserver.csproj b/MMOTestServer/MMOserver/MMOserver.csproj index ddb6123..3f9edfb 100644 --- a/MMOTestServer/MMOserver/MMOserver.csproj +++ b/MMOTestServer/MMOserver/MMOserver.csproj @@ -11,12 +11,21 @@ + + + .dockerignore + + + Always + + + diff --git a/MMOTestServer/MMOserver/Program.cs b/MMOTestServer/MMOserver/Program.cs index 1d012e0..c79dc6e 100644 --- a/MMOTestServer/MMOserver/Program.cs +++ b/MMOTestServer/MMOserver/Program.cs @@ -1,4 +1,6 @@ +using Microsoft.Extensions.Configuration; using MMOserver.Game; +using MMOserver.RDB; using Serilog; namespace MMOserver; @@ -9,6 +11,15 @@ class Program { // .MinimumLevel.Warning() // Warning 이상만 출력 배포시 + IConfigurationRoot config = new ConfigurationBuilder() + .SetBasePath(AppContext.BaseDirectory) + .AddJsonFile("config.json", optional: true) // 로컬 개발용 + .AddEnvironmentVariables() // 도커 배포용 + .Build(); + + // DB 연결 + // DbConnectionFactory dbFactory = new DbConnectionFactory(config); + string timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"); Log.Logger = new LoggerConfiguration() diff --git a/MMOTestServer/MMOserver/RDB/DbConnectionFactory.cs b/MMOTestServer/MMOserver/RDB/DbConnectionFactory.cs new file mode 100644 index 0000000..75c4843 --- /dev/null +++ b/MMOTestServer/MMOserver/RDB/DbConnectionFactory.cs @@ -0,0 +1,31 @@ +using System.Data; +using Microsoft.Extensions.Configuration; +using MySql.Data.MySqlClient; +using ServerLib.RDB.Database; + +namespace MMOserver.RDB; + +public class DbConnectionFactory : IDbConnectionFactory +{ + private readonly string connectionString; + + public DbConnectionFactory(IConfiguration config) + { + IConfigurationSection db = config.GetSection("Database"); + connectionString = $"Server={db["Host"]};" + + $"Port={db["Port"]};" + + $"Database={db["Name"]};" + + $"User={db["User"]};" + + $"Password={db["Password"]};" + + $"Pooling=true;" + + $"MinimumPoolSize={db["Pooling:MinimumPoolSize"]};" + + $"MaximumPoolSize={db["Pooling:MaximumPoolSize"]};" + + $"ConnectionTimeout={db["Pooling:ConnectionTimeout"]};" + + $"ConnectionIdleTimeout={db["Pooling:ConnectionIdleTimeout"]};"; + } + + public IDbConnection CreateConnection() + { + return new MySqlConnection(connectionString); + } +} diff --git a/MMOTestServer/MMOserver/RDB/Handlers/TestHandlers.cs b/MMOTestServer/MMOserver/RDB/Handlers/TestHandlers.cs new file mode 100644 index 0000000..8cf3d9b --- /dev/null +++ b/MMOTestServer/MMOserver/RDB/Handlers/TestHandlers.cs @@ -0,0 +1,48 @@ +using MMOserver.RDB.Models; +using MMOserver.RDB.Services; +using ServerLib.RDB.Handlers; + +namespace MMOserver.RDB.Handlers; + +public class TestHandler +{ + private readonly TestService _testService; + + public TestHandler(TestService testService) => _testService = testService; + + public async Task GetTestAsync(int id) + { + Test? result = await _testService.GetTestAsync(id); + return result is null + ? HandlerHelper.Error("Test not found") + : HandlerHelper.Success(result); + } + + public async Task GetTestByUuidAsync(string uuid) + { + Test? result = await _testService.GetTestByUuidAsync(uuid); + return result is null + ? HandlerHelper.Error("Test not found") + : HandlerHelper.Success(result); + } + + public async Task GetAllTestsAsync() + { + return HandlerHelper.Success(await _testService.GetAllTestsAsync()); + } + + public async Task CreateTestAsync(int testA, string testB, double testC) + { + return HandlerHelper.Success(await _testService.CreateTestAsync(testA, testB, testC)); + } + + public async Task UpdateTestAsync(int id, int? testA, string? testB, double? testC) + { + return HandlerHelper.Success(await _testService.UpdateTestAsync(id, testA, testB, testC)); + } + + public async Task DeleteTestAsync(int id) + { + return HandlerHelper.Success(await _testService.DeleteTestAsync(id)); + } +} diff --git a/MMOTestServer/MMOserver/RDB/Models/TestM.cs b/MMOTestServer/MMOserver/RDB/Models/TestM.cs new file mode 100644 index 0000000..ea433fe --- /dev/null +++ b/MMOTestServer/MMOserver/RDB/Models/TestM.cs @@ -0,0 +1,16 @@ +using Dapper.Contrib.Extensions; + +namespace MMOserver.RDB.Models; + +// 테이블 entity +[Table("test")] +public class Test +{ + [Key] + public int Id { get; set; } + public int TestA { get; set; } + public string? TestB { get; set; } + public double TestC { get; set; } + public string? Uuid { get; set; } // UNIQUE +} + diff --git a/MMOTestServer/MMOserver/RDB/Repositories/TestRepository.cs b/MMOTestServer/MMOserver/RDB/Repositories/TestRepository.cs new file mode 100644 index 0000000..0925dee --- /dev/null +++ b/MMOTestServer/MMOserver/RDB/Repositories/TestRepository.cs @@ -0,0 +1,18 @@ +using MMOserver.RDB.Models; +using ServerLib.RDB.Database; +using ServerLib.RDB.Repositories; + +namespace MMOserver.RDB.Repositories; + +// 실제 호출할 쿼리들 +public class TestRepository : ARepository +{ + public TestRepository(DbConnectionFactory factory) : base(factory) + { + } + + public async Task GetByUuidAsync(string uuid) + { + return await QueryFirstOrDefaultAsync("SELECT * FROM test WHERE uuid = @Uuid", new { Uuid = uuid }); + } +} diff --git a/MMOTestServer/MMOserver/RDB/Services/TestService.cs b/MMOTestServer/MMOserver/RDB/Services/TestService.cs new file mode 100644 index 0000000..6aff090 --- /dev/null +++ b/MMOTestServer/MMOserver/RDB/Services/TestService.cs @@ -0,0 +1,57 @@ +using MMOserver.RDB.Models; +using MMOserver.RDB.Repositories; + +namespace MMOserver.RDB.Services; + +public class TestService +{ + private readonly TestRepository repo; + + public TestService(TestRepository repo) + { + this.repo = repo; + } + + public Task GetTestAsync(int id) + { + return repo.GetByIdAsync(id); + } + + public Task GetTestByUuidAsync(string uuid) + { + return repo.GetByUuidAsync(uuid); + } + + public Task> GetAllTestsAsync() + { + return repo.GetAllAsync(); + } + + public async Task CreateTestAsync(int testA, string testB, double testC) + { + if (string.IsNullOrWhiteSpace(testB)) + { + throw new ArgumentException("TestB is required"); + } + + return await repo.InsertAsync(new Test { TestA = testA, TestB = testB, TestC = testC, Uuid = Guid.NewGuid().ToString() }); + } + + public async Task UpdateTestAsync(int id, int? testA, string? testB, double? testC) + { + Test entity = await repo.GetByIdAsync(id) ?? throw new KeyNotFoundException("Test not found"); + + entity.TestA = testA ?? entity.TestA; + entity.TestB = testB ?? entity.TestB; + entity.TestC = testC ?? entity.TestC; + + return await repo.UpdateAsync(entity); + } + + public async Task DeleteTestAsync(int id) + { + Test entity = await repo.GetByIdAsync(id) ?? throw new KeyNotFoundException("Test not found"); + + return await repo.DeleteAsync(entity); + } +} diff --git a/MMOTestServer/MMOserver/config.json b/MMOTestServer/MMOserver/config.json new file mode 100644 index 0000000..9b09330 --- /dev/null +++ b/MMOTestServer/MMOserver/config.json @@ -0,0 +1,15 @@ +{ + "Database": { + "Host": "localhost", + "Port": "0000", + "Name": "XXXX", + "User": "root", + "Password": "11212121", + "Pooling": { + "MinimumPoolSize": "5", + "MaximumPoolSize": "100", + "ConnectionTimeout": "30", + "ConnectionIdleTimeout": "180" + } + } +} diff --git a/MMOTestServer/ServerLib/RDB/Database/DbConnectionFactory.cs b/MMOTestServer/ServerLib/RDB/Database/DbConnectionFactory.cs new file mode 100644 index 0000000..90b32b5 --- /dev/null +++ b/MMOTestServer/ServerLib/RDB/Database/DbConnectionFactory.cs @@ -0,0 +1,8 @@ +using System.Data; + +namespace ServerLib.RDB.Database; + +public interface IDbConnectionFactory +{ + public IDbConnection CreateConnection(); +} diff --git a/MMOTestServer/ServerLib/RDB/Handlers/HelperHandler.cs b/MMOTestServer/ServerLib/RDB/Handlers/HelperHandler.cs new file mode 100644 index 0000000..db7308d --- /dev/null +++ b/MMOTestServer/ServerLib/RDB/Handlers/HelperHandler.cs @@ -0,0 +1,22 @@ +using System.Text.Json; + +namespace ServerLib.RDB.Handlers; + +public static class HandlerHelper +{ + public static string Success(T data) + { + return JsonSerializer.Serialize(new Response { Success = true, Data = data }); + } + + public static string Error(string message) + { + return JsonSerializer.Serialize(new Response { Success = false, Error = message }); + } + + public static T? Deserialize(string payload) + { + try { return JsonSerializer.Deserialize(payload); } + catch { return default; } + } +} diff --git a/MMOTestServer/ServerLib/RDB/Handlers/Response.cs b/MMOTestServer/ServerLib/RDB/Handlers/Response.cs new file mode 100644 index 0000000..43b7cb6 --- /dev/null +++ b/MMOTestServer/ServerLib/RDB/Handlers/Response.cs @@ -0,0 +1,8 @@ +namespace ServerLib.RDB.Handlers; + +public class Response +{ + public bool Success { get; init; } + public T? Data { get; init; } + public string? Error { get; init; } +} diff --git a/MMOTestServer/ServerLib/RDB/Repositories/ARepository.cs b/MMOTestServer/ServerLib/RDB/Repositories/ARepository.cs new file mode 100644 index 0000000..f5b6bbf --- /dev/null +++ b/MMOTestServer/ServerLib/RDB/Repositories/ARepository.cs @@ -0,0 +1,90 @@ +using System.Data; +using Dapper; +using Dapper.Contrib.Extensions; +using ServerLib.RDB.Database; + +namespace ServerLib.RDB.Repositories; + +public abstract class ARepository where T : class +{ + private readonly IDbConnectionFactory factory; + + protected ARepository(IDbConnectionFactory factory) + { + this.factory = factory; + } + + protected IDbConnection CreateConnection() + { + return factory.CreateConnection(); + } + + // Dapper.Contrib 기본 CRUD + public async Task GetByIdAsync(int id) + { + IDbConnection conn = CreateConnection(); + return await conn.GetAsync(id); + } + + public async Task> GetAllAsync() + { + IDbConnection conn = CreateConnection(); + return await conn.GetAllAsync(); + } + + public async Task InsertAsync(T entity) + { + IDbConnection conn = CreateConnection(); + return await conn.InsertAsync(entity); + } + + public async Task UpdateAsync(T entity) + { + IDbConnection conn = CreateConnection(); + return await conn.UpdateAsync(entity); + } + + public async Task DeleteAsync(T entity) + { + IDbConnection conn = CreateConnection(); + return await conn.DeleteAsync(entity); + } + + // 커스텀 쿼리 헬퍼 (복잡한 쿼리용) + protected async Task> QueryAsync(string sql, object? param = null) + { + IDbConnection conn = CreateConnection(); + return await conn.QueryAsync(sql, param); + } + + protected async Task QueryFirstOrDefaultAsync(string sql, object? param = null) + { + IDbConnection conn = CreateConnection(); + return await conn.QueryFirstOrDefaultAsync(sql, param); + } + + protected async Task ExecuteAsync(string sql, object? param = null) + { + IDbConnection conn = CreateConnection(); + return await conn.ExecuteAsync(sql, param); + } + + // 트랜잭션 헬퍼 + protected async Task WithTransactionAsync(Func> action) + { + IDbConnection conn = CreateConnection(); + conn.Open(); + IDbTransaction tx = conn.BeginTransaction(); + try + { + TResult result = await action(conn, tx); + tx.Commit(); + return result; + } + catch + { + tx.Rollback(); + throw; + } + } +} diff --git a/MMOTestServer/ServerLib/ServerLib.csproj b/MMOTestServer/ServerLib/ServerLib.csproj index 1da3505..c8ed13a 100644 --- a/MMOTestServer/ServerLib/ServerLib.csproj +++ b/MMOTestServer/ServerLib/ServerLib.csproj @@ -8,7 +8,11 @@ + + + + diff --git a/ReadMe.md b/ReadMe.md new file mode 100644 index 0000000..c77591c --- /dev/null +++ b/ReadMe.md @@ -0,0 +1,73 @@ +# DB 사용법 + +## 구조 +``` +MMOServer (실행 프로젝트 Test파일들은 임시용) +├── RDB/ +├──── Handlers/ +│ └──── TestHandler.cs +├──── Models/ +│ └──── TestM.cs +├──── Repositories/ +│ └──── TestRepository.cs +└──── Services/ + └──── TestService.cs + +ServerLib (DLL) +├── RDB/ +├──── Database/ +│ └── DbConnectionFactory.cs +├──── Handlers/ +│ ├── HandlerHelper.cs +│ └── Response.cs +└──── Repositories/ + └── ARepository.cs +``` + +--- + +## 데이터 흐름 + +``` +호출부 (Packet 등) + ↓ 파라미터 전달 +Handler → Service 호출 + 응답 포맷팅 (string) + ↓ +Service → 비즈니스 로직, 유효성 검사, 예외 throw + ↓ +Repository → DB 접근 (TestM? / bool / long 반환) + ↓ +MySQL +``` + +--- + +## 반환 타입 정리 + +| 레이어 | 반환 타입 | +|--------|---------------------------------------------------| +| Repository | `class?` / `IEnumerable` / `long` / `bool` | +| Service | 동일 (그대로 올림) | +| Handler | `string` (JSON 포장) | + +--- + +## 새 테이블 추가 시 + +1. `MMOServer/RDB/Models/` 에 Model 추가 +2. `MMOServer/RDB/Repositories/` 에 Repository 추가 (ARepository 상속) +3. `MMOServer/RDB/Services/` 에 Service 추가 +4. `MMOServer/RDB/Handlers/` 에 Handler 추가 + +**ServerLib (DLL) 은 건드릴 필요 없음.** + +--- + +## Connection Pool 설정(아직 구성 안됨) + +``` +Server=localhost;Database=mydb;User=root;Password=1234; +Pooling=true;MinimumPoolSize=5;MaximumPoolSize=100; +ConnectionTimeout=30;ConnectionIdleTimeout=180; +``` +