深入学习 ODB(八):实战——构建完整的游戏存档系统

系列导航:编译器与注解 | 连接与事务 | 对象关系 | 类型安全查询 | 继承与视图 | 迁移与多库 | 性能与实践 | 实战项目(本文) 引言:从零到完整 经过前七篇的学习,我们掌握了 ODB 的所有核心能力。本篇将综合运用它们,构建一个完整可运行的游戏存档系统——这不是简化的教学示例,而是接近生产级别的架构。 我们要实现以下游戏系统: 1 2 3 4 5 6 7 ┌─────────────────────────────────────────────────────────┐ │ 游戏存档系统 │ ├─────────────┬──────────────┬────────────┬───────────────┤ │ 玩家系统 │ 背包系统 │ 公会系统 │ 交易系统 │ │ Player │ Inventory │ Guild │ TradeLog │ │ Profile │ Equipment │ Members │ AuditLog │ └─────────────┴──────────────┴────────────┴───────────────┘ 1. 需求分析与数据建模 1.1 E-R 关系图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ┌──────────────┐ │ PlayerProfile│ │ (1:1) │ └──────┬───────┘ │ ┌──────────┐ ┌──────┴───────┐ ┌──────────────┐ │ Equipment│◀── 1:N ┤ Player ├ M:N ──▶│ Guild │ │ (装备栏) │ │ │ │ │ └──────────┘ └──────┬───────┘ └──────────────┘ │ ┌──────┴───────┐ │InventoryItem │ │ (1:N 背包) │ └──────────────┘ ┌──────────────┐ │ TradeLog │ ← 独立审计表 └──────────────┘ 1.2 实体清单 实体 说明 关键关系 BaseEntity 抽象基类,提供 id / createdAt / updatedAt 所有实体继承 Player 玩家核心数据 一对一 Profile,一对多背包/装备,多对多公会 PlayerProfile 玩家扩展档案 反向引用 Player InventoryItem 背包道具 多对一 Player Equipment 装备栏(穿戴中的装备) 多对一 Player Guild 公会 多对多 Player TradeLog 交易日志 独立表,通过 playerId 关联 2. ODB 对象定义 2.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 // db/objects/base_entity.hxx #ifndef BASE_ENTITY_HXX #define BASE_ENTITY_HXX #include <string> #include <cstdint> #include <odb/core.hxx> // 所有实体的公共字段 #pragma db object abstract class BaseEntity { public: uint64_t id() const { return id_; } const std::string& createdAt() const { return createdAt_; } const std::string& updatedAt() const { return updatedAt_; } void setCreatedAt(const std::string& t) { createdAt_ = t; } void setUpdatedAt(const std::string& t) { updatedAt_ = t; } protected: friend class odb::access; #pragma db id auto uint64_t id_{0}; #pragma db type("DATETIME") column("created_at") not_null std::string createdAt_; #pragma db type("DATETIME") column("updated_at") not_null std::string updatedAt_; }; #endif 2.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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 // db/objects/player_profile.hxx #ifndef PLAYER_PROFILE_HXX #define PLAYER_PROFILE_HXX #include <string> #include <odb/core.hxx> #include <odb/lazy-ptr.hxx> #include "base_entity.hxx" class Player; #pragma db object table("player_profile") class PlayerProfile : public BaseEntity { public: PlayerProfile() = default; PlayerProfile(const std::string& avatar, const std::string& signature) : avatar_(avatar), signature_(signature) {} const std::string& avatar() const { return avatar_; } const std::string& signature() const { return signature_; } int totalKills() const { return totalKills_; } int totalDeaths() const { return totalDeaths_; } int totalPlayTime() const { return totalPlayTime_; } void setAvatar(const std::string& a) { avatar_ = a; } void setSignature(const std::string& s) { signature_ = s; } void addKill() { ++totalKills_; } void addDeath() { ++totalDeaths_; } void addPlayTime(int seconds) { totalPlayTime_ += seconds; } odb::lazy_weak_ptr<Player>& owner() { return owner_; } private: friend class odb::access; #pragma db inverse(profile_) odb::lazy_weak_ptr<Player> owner_; #pragma db type("VARCHAR(256)") std::string avatar_; #pragma db type("VARCHAR(128)") std::string signature_; #pragma db column("total_kills") not_null int totalKills_{0}; #pragma db column("total_deaths") not_null int totalDeaths_{0}; #pragma db column("total_play_time") not_null int totalPlayTime_{0}; // 累计在线秒数 }; #endif 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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 // db/objects/player.hxx #ifndef PLAYER_HXX #define PLAYER_HXX #include <string> #include <vector> #include <map> #include <memory> #include <cstdint> #include <odb/core.hxx> #include <odb/lazy-ptr.hxx> #include <odb/section.hxx> #include "base_entity.hxx" class PlayerProfile; class InventoryItem; class Equipment; class Guild; // 玩家状态枚举 enum class PlayerStatus : int { Active = 0, // 正常 Banned = 1, // 封禁 Deleted = 2 // 已删除(软删除) }; #pragma db model version(1, 1) #pragma db object table("player") class Player : public BaseEntity { public: Player() = default; Player(const std::string& name, int level = 1) : name_(name), level_(level) {} // --- 核心字段访问 --- const std::string& name() const { return name_; } int level() const { return level_; } uint64_t exp() const { return exp_; } uint64_t gold() const { return gold_; } PlayerStatus status() const { return status_; } void setName(const std::string& n) { name_ = n; } void setLevel(int lv) { level_ = lv; } void addExp(uint64_t e) { exp_ += e; } void addGold(uint64_t g) { gold_ += g; } void deductGold(uint64_t g) { gold_ -= g; } void setStatus(PlayerStatus s) { status_ = s; } // --- 关系访问 --- odb::lazy_shared_ptr<PlayerProfile>& profile() { return profile_; } std::vector<odb::lazy_shared_ptr<InventoryItem>>& inventory() { return inventory_; } std::vector<odb::lazy_shared_ptr<Equipment>>& equipments() { return equipments_; } std::vector<odb::lazy_shared_ptr<Guild>>& guilds() { return guilds_; } // --- 扩展段访问 --- odb::section& skillSection() { return skillSection_; } const std::map<uint64_t, int>& skills() const { return skills_; } void setSkillLevel(uint64_t skillId, int level) { skills_[skillId] = level; } private: friend class odb::access; // ===== 核心字段(默认加载)===== #pragma db type("VARCHAR(32)") not_null unique std::string name_; #pragma db not_null index int level_{1}; uint64_t exp_{0}; uint64_t gold_{0}; #pragma db column("vip_level") not_null int vipLevel_{0}; #pragma db not_null PlayerStatus status_{PlayerStatus::Active}; // ===== 关系映射 ===== // 一对一:玩家档案 #pragma db not_null odb::lazy_shared_ptr<PlayerProfile> profile_; // 一对多:背包道具 #pragma db inverse(owner_) value_not_null std::vector<odb::lazy_shared_ptr<InventoryItem>> inventory_; // 一对多:装备栏 #pragma db inverse(wearer_) value_not_null std::vector<odb::lazy_shared_ptr<Equipment>> equipments_; // 多对多:加入的公会 #pragma db inverse(members_) value_not_null std::vector<odb::lazy_shared_ptr<Guild>> guilds_; // ===== 扩展段(按需加载)===== #pragma db load(lazy) update(change) section(skillSection_) \ table("player_skills") key_column("skill_id") value_column("level") std::map<uint64_t, int> skills_; #pragma db transient odb::section skillSection_; }; #endif 2.3 背包与装备 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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 // db/objects/inventory_item.hxx #ifndef INVENTORY_ITEM_HXX #define INVENTORY_ITEM_HXX #include <cstdint> #include <odb/core.hxx> #include <odb/lazy-ptr.hxx> #include "base_entity.hxx" class Player; // 道具品质 enum class ItemQuality : int { White = 1, Green = 2, Blue = 3, Purple = 4, Orange = 5 }; #pragma db object table("inventory") class InventoryItem : public BaseEntity { public: InventoryItem() = default; InventoryItem(uint64_t templateId, int count, int slot, ItemQuality quality) : templateId_(templateId), count_(count), slot_(slot), quality_(quality) {} uint64_t templateId() const { return templateId_; } int count() const { return count_; } int slot() const { return slot_; } ItemQuality quality() const { return quality_; } void setCount(int c) { count_ = c; } void addCount(int n) { count_ += n; } void setSlot(int s) { slot_ = s; } odb::lazy_shared_ptr<Player>& owner() { return owner_; } private: friend class odb::access; #pragma db not_null odb::lazy_shared_ptr<Player> owner_; #pragma db column("template_id") not_null index uint64_t templateId_{0}; #pragma db not_null int count_{1}; #pragma db not_null int slot_{0}; #pragma db not_null ItemQuality quality_{ItemQuality::White}; }; #endif 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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 // db/objects/equipment.hxx #ifndef EQUIPMENT_HXX #define EQUIPMENT_HXX #include <cstdint> #include <odb/core.hxx> #include <odb/lazy-ptr.hxx> #include "base_entity.hxx" class Player; // 装备部位 enum class EquipSlot : int { Head = 0, Chest = 1, Legs = 2, Feet = 3, MainHand = 4, OffHand = 5, Ring = 6, Necklace = 7 }; #pragma db object table("equipment") class Equipment : public BaseEntity { public: Equipment() = default; Equipment(uint64_t templateId, EquipSlot slot, int attack, int defense, int enhanceLevel) : templateId_(templateId), slot_(slot), attack_(attack), defense_(defense), enhanceLevel_(enhanceLevel) {} uint64_t templateId() const { return templateId_; } EquipSlot slot() const { return slot_; } int attack() const { return attack_; } int defense() const { return defense_; } int enhanceLevel() const { return enhanceLevel_; } int maxEnhance() const { return maxEnhance_; } void setEnhanceLevel(int lv) { enhanceLevel_ = lv; } void addAttack(int a) { attack_ += a; } void addDefense(int d) { defense_ += d; } odb::lazy_shared_ptr<Player>& wearer() { return wearer_; } private: friend class odb::access; #pragma db not_null odb::lazy_shared_ptr<Player> wearer_; #pragma db column("template_id") not_null uint64_t templateId_{0}; #pragma db not_null EquipSlot slot_{EquipSlot::MainHand}; int attack_{0}; int defense_{0}; #pragma db column("enhance_level") not_null int enhanceLevel_{0}; #pragma db column("max_enhance") not_null int maxEnhance_{15}; }; #endif 2.4 公会 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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 // db/objects/guild.hxx #ifndef GUILD_HXX #define GUILD_HXX #include <string> #include <vector> #include <cstdint> #include <odb/core.hxx> #include <odb/lazy-ptr.hxx> #include "base_entity.hxx" class Player; #pragma db object table("guild") class Guild : public BaseEntity { public: Guild() = default; Guild(const std::string& name, uint64_t leaderId) : name_(name), leaderId_(leaderId) {} const std::string& name() const { return name_; } uint64_t leaderId() const { return leaderId_; } int level() const { return level_; } const std::string& announcement() const { return announcement_; } void setLevel(int lv) { level_ = lv; } void setLeaderId(uint64_t id) { leaderId_ = id; } void setAnnouncement(const std::string& a) { announcement_ = a; } std::vector<odb::lazy_shared_ptr<Player>>& members() { return members_; } private: friend class odb::access; #pragma db type("VARCHAR(32)") not_null unique std::string name_; #pragma db column("leader_id") not_null uint64_t leaderId_{0}; #pragma db not_null int level_{1}; #pragma db column("max_members") not_null int maxMembers_{50}; #pragma db type("VARCHAR(256)") std::string announcement_; #pragma db value_not_null unordered table("guild_members") std::vector<odb::lazy_shared_ptr<Player>> members_; }; #endif 2.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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 // db/objects/trade_log.hxx #ifndef TRADE_LOG_HXX #define TRADE_LOG_HXX #include <string> #include <cstdint> #include <odb/core.hxx> #include "base_entity.hxx" enum class TradeAction : int { Buy = 1, // 商店购买 Sell = 2, // 商店出售 Transfer = 3, // 玩家间转账 Enhance = 4, // 装备强化消耗 Reward = 5 // 系统奖励 }; #pragma db object table("trade_log") class TradeLog : public BaseEntity { public: TradeLog() = default; TradeLog(uint64_t playerId, TradeAction action, uint64_t itemId, int quantity, uint64_t goldChange, const std::string& detail) : playerId_(playerId), action_(action), itemId_(itemId), quantity_(quantity), goldChange_(goldChange), detail_(detail) {} uint64_t playerId() const { return playerId_; } TradeAction action() const { return action_; } uint64_t itemId() const { return itemId_; } int quantity() const { return quantity_; } uint64_t goldChange() const { return goldChange_; } const std::string& detail() const { return detail_; } private: friend class odb::access; #pragma db column("player_id") not_null index uint64_t playerId_{0}; #pragma db not_null TradeAction action_{TradeAction::Buy}; #pragma db column("item_id") uint64_t itemId_{0}; int quantity_{0}; #pragma db column("gold_change") uint64_t goldChange_{0}; #pragma db type("VARCHAR(256)") std::string detail_; }; #endif 2.6 View 定义 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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 // db/views/game_views.hxx #ifndef GAME_VIEWS_HXX #define GAME_VIEWS_HXX #include <string> #include <cstdint> #include <odb/core.hxx> #include "db/objects/player.hxx" #include "db/objects/guild.hxx" #include "db/objects/trade_log.hxx" // 排行榜条目 #pragma db view object(Player) struct LeaderboardEntry { #pragma db column(Player::id_) uint64_t id; #pragma db column(Player::name_) std::string name; #pragma db column(Player::level_) int level; #pragma db column(Player::exp_) uint64_t exp; }; // 全服统计 #pragma db view object(Player) struct ServerStats { #pragma db column("count(" + Player::id_ + ")") uint64_t totalPlayers; #pragma db column("avg(" + Player::level_ + ")") double avgLevel; #pragma db column("max(" + Player::level_ + ")") int maxLevel; #pragma db column("sum(" + Player::gold_ + ")") uint64_t totalGold; }; // 公会战力排名 #pragma db view object(Guild) object(Player: Guild::members_) struct GuildPowerRank { #pragma db column(Guild::id_) uint64_t guildId; #pragma db column(Guild::name_) std::string guildName; #pragma db column("count(" + Player::id_ + ")") int memberCount; #pragma db column("sum(" + Player::level_ + ")") int totalPower; }; // 每日交易汇总 #pragma db view query( \ "SELECT DATE(created_at) AS trade_date, " \ " action AS trade_action, " \ " COUNT(*) AS trade_count, " \ " SUM(gold_change) AS total_gold " \ "FROM trade_log " \ "GROUP BY DATE(created_at), action " \ "ORDER BY trade_date DESC, action ASC") struct DailyTradeReport { #pragma db column("trade_date") type("DATE") std::string tradeDate; #pragma db column("trade_action") int tradeAction; #pragma db column("trade_count") uint64_t tradeCount; #pragma db column("total_gold") uint64_t totalGold; }; #endif 3. Repository 层实现 Repository 层封装所有数据库操作,让业务层不直接依赖 ODB 类型。 ...

July 12, 2025 · 24 min · 4920 words

深入学习 ODB(七):性能调优与生产最佳实践

系列导航:编译器与注解 | 连接与事务 | 对象关系 | 类型安全查询 | 继承与视图 | 迁移与多库 | 性能与实践(本文) | 实战项目 引言:万人同服的存档难题 某 MMO 游戏服务器,5000 人同时在线,每 5 分钟执行一次全服存档: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 朴素实现:逐个存档 void saveAllPlayers(odb::database& db, const std::vector<Player*>& onlinePlayers) { for (auto* player : onlinePlayers) { odb::transaction t(db.begin()); db.update(*player); t.commit(); } } // 5000 个玩家 × 每次 1 个事务 × 每事务 1 条 UPDATE // = 5000 次网络往返 + 5000 次事务提交 // 耗时:约 15~30 秒(取决于网络延迟) // 这期间游戏逻辑被阻塞,玩家感受到明显卡顿 这段代码有三个性能杀手: ...

July 5, 2025 · 13 min · 2726 words

深入学习 ODB(六):Schema 演进与多数据库适配

系列导航:编译器与注解 | 连接与事务 | 对象关系 | 类型安全查询 | 继承与视图 | 迁移与多库(本文) | 性能与实践 | 实战项目 引言:游戏版本更新的数据库噩梦 游戏上线后,每次版本更新几乎都涉及数据库变更: v1.1:给 Player 加 vip_level 字段 v1.2:新增"成就系统"表 v1.3:把 gold 从 INT 改为 BIGINT(金币膨胀了) v2.0:重构道具表,拆分装备和消耗品 传统做法是手写 ALTER TABLE 脚本: 1 2 3 4 5 6 7 8 -- v1.1_add_vip.sql ALTER TABLE player ADD COLUMN vip_level INT NOT NULL DEFAULT 0; -- v1.2_achievement.sql CREATE TABLE achievement (...); -- v1.3_gold_bigint.sql ALTER TABLE player MODIFY COLUMN gold BIGINT UNSIGNED DEFAULT 0; 这套流程的问题: ...

June 28, 2025 · 12 min · 2460 words

深入学习 ODB(五):继承策略与 View 的强大抽象

系列导航:编译器与注解 | 连接与事务 | 对象关系 | 类型安全查询 | 继承与视图(本文) | 迁移与多库 | 性能与实践 | 实战项目 引言:道具系统的继承难题 游戏中的道具系统天然适合用继承建模: 1 2 3 4 Item(道具基类) ├── Weapon(武器) — 攻击力、攻速、暴击率 ├── Armor(防具) — 防御力、抗性、耐久度 └── Consumable(消耗品)— 恢复量、冷却时间、持续时间 在 C++ 中,这是一个干净的继承体系。但映射到关系数据库时,问题来了——关系数据库没有继承的概念。一张表怎么存不同子类的字段?ODB 提供了三种策略来解决这个问题,各有取舍。 此外,游戏运营经常需要统计数据——“全服多少人在线”、“公会战力排名”、“每日交易额”。加载完整对象再聚合太慢了,ODB 的 View(视图)让你直接获取投影和聚合结果,不产生完整对象。 1. 继承映射三策略 1.1 共同的基类定义 三种策略使用相同的 C++ 继承体系,只是 pragma 配置不同: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // item_base.hxx — 道具基类(所有策略共用) #include <string> #include <cstdint> #include <odb/core.hxx> // 道具品质枚举 enum class ItemQuality : int { White = 1, // 白色 - 普通 Green = 2, // 绿色 - 优秀 Blue = 3, // 蓝色 - 精良 Purple = 4, // 紫色 - 史诗 Orange = 5 // 橙色 - 传说 }; 1.2 策略一:单表继承(Table Per Hierarchy) 所有子类共用一张表,通过鉴别器列(discriminator)区分类型: ...

June 21, 2025 · 15 min · 3043 words

深入学习 ODB(四):ODB Query Language——类型安全的查询之道

系列导航:编译器与注解 | 连接与事务 | 对象关系 | 类型安全查询(本文) | 继承与视图 | 迁移与多库 | 性能与实践 | 实战项目 引言:手写 SQL 查询的隐患 游戏服务器中,查询无处不在——排行榜、道具筛选、好友搜索、日志审计。手写 SQL 最大的风险是编译器看不见的错误: 1 2 3 4 5 6 7 8 9 10 11 12 // ❌ 手写 SQL:字段名拼错,编译不报错,运行时才崩 auto result = mysql_query(conn, "SELECT * FROM player WHERE levle > 50 ORDER BY levle DESC"); // ^^^^ ^^^^ // 拼成了 levle,MySQL 直接报错 // 但编译器对此一无所知 // ❌ 类型错误:用字符串比较数字 auto result = mysql_query(conn, "SELECT * FROM player WHERE level > '50'"); // ^^^^ // 隐式类型转换,可能有意外行为 ODB 的查询系统从根本上解决了这个问题——字段名是 C++ 符号,类型是编译期检查的。拼错字段名?编译不通过。类型不匹配?编译不通过。 ...

June 14, 2025 · 11 min · 2326 words

深入学习 ODB(三):对象关系——一对一、一对多与多对多

系列导航:编译器与注解 | 连接与事务 | 对象关系(本文) | 类型安全查询 | 继承与视图 | 迁移与多库 | 性能与实践 | 实战项目 引言:从单表到关系网络 前两篇我们学会了单个 Player 对象的持久化。但真实的游戏系统远不止一张表: 1 2 3 4 5 6 7 Player ──── 1:1 ────▶ PlayerProfile (玩家档案:头像、签名、战斗统计) │ ├─── 1:N ────▶ InventoryItem[] (背包:拥有多个道具) │ ├─── 1:N ────▶ Equipment[] (装备栏:穿戴的装备) │ └─── M:N ────▶ Guild[] (公会:多个玩家加入多个公会) 在关系数据库中,这些关系通过外键表达。而在 C++ 中,我们习惯用指针和容器表达。ODB 的工作就是在这两种世界观之间架起桥梁。 本篇将逐步讲解 ODB 如何用智能指针和 STL 容器映射所有类型的对象关系。 1. 关系映射基础概念 1.1 数据库外键 vs C++ 指针 数据库世界 C++ 世界 ODB 桥接方式 外键列(player_id BIGINT) std::shared_ptr<Player> ODB 自动处理 load/persist JOIN 查询 解引用指针 ptr->name() 延迟或急切加载 联接表(junction table) std::vector<shared_ptr<T>> ODB 自动维护联接表 1.2 ODB 的指针类型选择 ODB 支持多种指针类型来表达对象引用: ...

June 7, 2025 · 16 min · 3204 words

深入学习 ODB(二):连接管理与事务的艺术

系列导航:编译器与注解 | 连接与事务(本文) | 对象关系 | 类型安全查询 | 继承与视图 | 迁移与多库 | 性能与实践 | 实战项目 引言:一次失败的道具购买 某天,游戏服务器日志里出现了一条玩家投诉: “我花了 500 金币买药水,金币扣了但背包里没有药水!” 排查代码发现问题出在这里: 1 2 3 4 5 6 7 8 9 10 11 // 错误示范:非原子操作 void buyItem(Player& player, uint64_t itemId, uint64_t price) { player.deductGold(price); updatePlayerGold(conn, player); // SQL: UPDATE player SET gold=? WHERE id=? // ← 假如这里服务器崩溃了... Item item(itemId, player.id()); insertItem(conn, item); // SQL: INSERT INTO inventory ... } 金币扣除成功提交了,但道具插入还没执行服务器就崩了。数据不一致。 这个问题的根本解法是 事务(Transaction):要么全部成功,要么全部回滚。ODB 用 RAII 语义让事务管理变得优雅且安全——即使代码抛异常,也不会产生"半完成"状态。 1. database 工厂与连接池 1.1 创建数据库连接 ODB 中,odb::database 是所有操作的入口。对于 MySQL 后端: ...

May 31, 2025 · 12 min · 2375 words

深入学习 ODB(一):编译期 ORM 的革命性思路

系列导航:编译器与注解(本文) | 连接与事务 | 对象关系 | 类型安全查询 | 继承与视图 | 迁移与多库 | 性能与实践 | 实战项目 引言:游戏服务器为什么需要 ORM? 假设你正在开发一款 MMO 游戏服务器,玩家下线时需要将角色数据存入 MySQL。最朴素的做法是: 1 2 3 4 5 6 7 8 9 10 11 12 13 // 传统方式:手动拼接 SQL void savePlayer(MYSQL* conn, const Player& p) { char sql[1024]; snprintf(sql, sizeof(sql), "REPLACE INTO player (id, name, level, exp, gold, created_at) " "VALUES (%lu, '%s', %d, %lu, %lu, '%s')", p.id, p.name.c_str(), p.level, p.exp, p.gold, p.createdAt.c_str()); mysql_query(conn, sql); // 没有类型检查、没有防注入、字段增减要同步改 SQL... } 这段代码有几个致命问题: ...

May 25, 2025 · 10 min · 2076 words