1 介绍YB.ORM
YB.ORM 旨在简化与关系数据库交互的 C++ 应用程序的开发。
对象关系映射器(ORM) 通过将数据库表映射到类并将表行映射到应用程序中的对象来工作,这种方法可能不是对每个数据库应用程序都是最佳的,但它被证明在需要复杂逻辑和事务处理的应用程序中是合理的。
虽然这是一个正在进行的项目,但大多数功能都已经完成。
YB.ORM 项目的目标是:
- 为 C++ 开发人员提供方
- 便的 API
- 保持C++的高性能
- 保持源代码在不同平台和编译器之间轻松移植
- 支持大多数主要的关系数据库管理系统(DBMS)
该工具使用了 Martin Fowler 在《企业应用程序架构模式》一书中解释的许多概念,即“延迟加载”、“身份映射”、“工作单元”等模式。同时,项目开发受到启发借助 Java 的Hibernate框架的强大功能,尤其是 Python 的SQLAlchemy设计。
对象关系映射
此部分可以参考我的另外一篇文章:https://blog.csdn.net/weixin_42887343/article/details/120664818
关系数据库现在非常普遍——从 Oracle 集群到嵌入式 SQLite 基于文件的数据库,关系数据库以具有列和行的矩形表的形式对数据进行操作。
从应用程序代码连接 SQL 数据库并不容易,看看普通的ODBC API 就知道了,针对数据库运行 SQL 语句的典型步骤可能包括以下内容:
- 连接数据库,提供参数:主机、端口、模式、用户名、密码等。
- 使用连接句柄,创建游标并准备SQL 语句,以文本形式提供 SQL 语句
- 使用游标句柄,可选择将输出参数绑定到您的输出变量,这些变量将在提取完成时接收它们的值
- 使用游标句柄,可选择将输入参数(位置参数或命名参数)绑定到您的输入变量
- 可选地为输入变量分配它们的值
- 使用游标,执行准备好的语句,可选择继续执行步骤 5
- 可选择获取结果集的下一行,如果确定则查看您的输出变量,可选择重复第 7 步
- 关闭游标
- 关闭连接
YB.ORM库的使用
使用 ORM 从模型定义开始。它可能看起来像一个带有表和关系的 XML 文件,或者像一个类声明中的内联宏,或者一个处理访问者的模板函数。此步骤可能需要也可能不需要代码生成或某种其他类型的预处理。这些变体中的每一个都有其自身的优点和缺点。
让我们考虑一个具有两个实体的示例架构:Client和Order。它们之间是一对多的关系:一个Client可能有零个,也可能有多个Orders,每一个都Order属于一个Client。Clients存储在 table 中client_tbl,而它们的Orders 存储在 table 中order_tbl。
在 SQL 级别,该关系可以表示为子表中的列引用父表中的主键列的外键约束。从 ORM 的角度来看,这种关系通常由对象的属性来表示。类的实例具有对象引用属性,引用类的单个父对象。从关系的另一端看,一个类的实例可能有一个对象集合属性(也称为“ ”),可以用来遍历它的所有子对象。client_id order_tblid client_tblOrderClientClientbackrefOrder
让我们定义映射模式以及两个类Client和Order。
#include "orm/domain_object.h"
#include "orm/domain_factory.h"
#include "orm/schema_decl.h"
class Order;
class Client: public Yb::DomainObject {
YB_DECLARE(Client, "client_tbl", "client_seq", "client",
YB_COL_PK(id, "id")
YB_COL_DATA(dt, "dt", DATETIME)
YB_COL_STR(name, "name", 100)
YB_COL_STR(email, "email", 100)
YB_COL_DATA(budget, "budget", DECIMAL)
YB_REL_ONE(Client, owner, Order, orders, Yb::Relation::Restrict, "client_id", 1, 1)
YB_COL_END)
public:
int get_info() const { return 42; }
};
class Order: public Yb::DomainObject {
YB_DECLARE(Order, "order_tbl", "order_seq", "order",
YB_COL_PK(id, "id")
YB_COL_FK(client_id, "client_id", "client_tbl", "id")
YB_COL(dt, "dt", DATETIME, 0, 0, Yb::Value("sysdate"), "", "", "", "")
YB_COL_STR(memo, "memo", 100)
YB_COL_DATA(total_sum, "total_sum", DECIMAL)
YB_COL_DATA(paid_sum, "paid_sum", DECIMAL)
YB_COL_DATA(paid_dt, "paid_dt", DATETIME)
YB_REL_MANY(Client, owner, Order, orders, Yb::Relation::Restrict, "client_id", 1, 1)
YB_COL_END)
public:
const Yb::Decimal to_be_paid() {
return total_sum - paid_sum.value(0);
}
};
这些类声明可以放在标题或.cpp文件中。您的.cpp文件中还需要两句话才能使魔术生效:
YB_DEFINE(Client)
YB_DEFINE(Order)
类Client和Order会自动获得一些新的数据成员和方法。现在类的每个对象都有映射属性(id, dt, , …)。name这些属性可用于以读取和写入模式访问列数据,以及检查是否存在缺失值 ( IS NULL)。
要控制映射类的实例,必须有一个类的实例Yb::Session,它负责加载/保存对象、跟踪更改、控制关系等。在创建时,Session将数据库方案传递给它。
int main() {
Yb::init_schema(); // gather all declarations in one schema
Yb::Session session(Yb::theSchema(), "sqlite+sqlite://./tut1.db");
session.create_schema(true); // create schema if necessary
现在你可以像那样立即使用域类:
Order order;
order.total_sum = Yb::Decimal("3.14");
order.paid_sum = Yb::Decimal(0);
order.save(session);
Client client;
client.name = "Some Name";
client.email = "some@email";
client.dt = Yb::now();
client.save(session);
order.owner = Client::Holder(client);
session.commit();
return 0;
}
您可以编译示例,链接库ybutil和yborm,然后它就可以运行了。如果你愿意,你可以打开日志记录来查看引擎盖下发生了什么:
#include "util/nlogger.h"
#include <iostream>
...
Yb::LogAppender appender(std::cerr);
Yb::init_schema(); // gather all declarations in one schema
Yb::Session session(Yb::theSchema(), "sqlite+sqlite://./tut1.db");
session.set_logger(Yb::ILogger::Ptr(new Yb::Logger(&appender)));
以下是特定于 SQLite 数据库引擎的日志消息:
14-10-27 14:19:38.489 21962/21962 DEBG sql: exec_direct: CREATE TABLE client_tbl (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
dt TIMESTAMP,
name VARCHAR(100),
email VARCHAR(100),
budget NUMERIC
)
14-10-27 14:19:38.818 21962/21962 DEBG sql: exec_direct: CREATE TABLE order_tbl (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
client_id INTEGER NOT NULL,
dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
memo VARCHAR(100),
total_sum NUMERIC,
paid_sum NUMERIC,
paid_dt TIMESTAMP
, FOREIGN KEY (client_id) REFERENCES client_tbl(id)
)
14-10-27 14:19:38.842 21962/21962 DEBG orm: flush started
14-10-27 14:19:38.843 21962/21962 DEBG sql: begin transaction
14-10-27 14:19:38.843 21962/21962 DEBG sql:
prepare: INSERT INTO client_tbl (dt, name, email, budget) VALUES (?, ?, ?, ?)
14-10-27 14:19:38.843 21962/21962 DEBG sql: bind: (DateTime, String, String, Decimal)
14-10-27 14:19:38.843 21962/21962 DEBG sql: exec prepared: p1="'2014-10-27
14:19:38'" p2="'Some Name'" p3="'some@email'" p4="NULL"
14-10-27 14:19:38.844 21962/21962 DEBG sql:
prepare: SELECT SEQ LID FROM SQLITE_SEQUENCE WHERE NAME = 'client_tbl'
14-10-27 14:19:38.844 21962/21962 DEBG sql: exec prepared:
14-10-27 14:19:38.844 21962/21962 DEBG sql: fetch: LID='1'
14-10-27 14:19:38.844 21962/21962 DEBG sql: fetch: no more rows
14-10-27 14:19:38.845 21962/21962 DEBG sql: prepare: INSERT INTO order_tbl
(client_id, dt, memo, total_sum, paid_sum, paid_dt) VALUES (?, ?, ?, ?, ?, ?)
14-10-27 14:19:38.845 21962/21962 DEBG sql: bind: (LongInt, DateTime, String, Decimal, Decimal, DateTime)
14-10-27 14:19:38.845 21962/21962 DEBG sql: exec prepared: p1="1"
p2="'2014-10-27 14:19:38'" p3="NULL" p4="3.14" p5="0" p6="NULL"
14-10-27 14:19:38.845 21962/21962 DEBG sql:
prepare: SELECT SEQ LID FROM SQLITE_SEQUENCE WHERE NAME = 'order_tbl'
14-10-27 14:19:38.846 21962/21962 DEBG sql: exec prepared:
14-10-27 14:19:38.846 21962/21962 DEBG sql: fetch: LID='1'
14-10-27 14:19:38.846 21962/21962 DEBG sql: fetch: no more rows
14-10-27 14:19:38.846 21962/21962 DEBG orm: flush finished OK
14-10-27 14:19:38.846 21962/21962 DEBG sql: commit
注意正确的插入顺序(第一个 - 父母,第二个 - 孩子)。这是通过对对象图进行拓扑排序来实现的。外键的值是自动分配的,主键的值也是如此。
如果我们从另一端操纵对象之间的链接,也可以达到同样的效果:
//order.owner = Client::Holder(client);
client.orders.insert(order);
域类对于构造查询特别有用。例如,我们需要一个针对特定客户订单的寻呼机,让我们从 30 到 39(含)之间获取商品:
#include <boost/foreach.hpp>
...
Yb::DomainResultSet<Order> rs = Yb::query<Order>(session)
.filter_by(Order::c.client_id == 32738)
.order_by(Order::c.dt)
.range(30, 40).all();
BOOST_FOREACH(Order order, rs) {
std::cout << order.id << ",";
}
在这里,我们可以看到在不同 SQL 方言中实现不同的功能。例如,对于 SQLite,将发出以下 SQL 代码:
SQL:
SELECT order_tbl.id, order_tbl.client_id, order_tbl.dt, order_tbl.memo,
order_tbl.total_sum, order_tbl.paid_sum, order_tbl.paid_dt
FROM order_tbl WHERE (order_tbl.client_id = ?)
ORDER BY order_tbl.dt
LIMIT ? OFFSET ?
positional params: (32738, 10, 30)
有关更多示例、下载和任何进一步信息,请访问项目主页https://sourceforge.net/projects/yborm/。
原文链接:https://www.codeproject.com/Articles/834803/Quick-Introduction-to-YB-ORM-Object-Relational-Map