-
Notifications
You must be signed in to change notification settings - Fork 20
ru_Tutorial2
Рассмотрим связь один-ко-многим на примере отношения Клиент — Заказы. У
одного клиента может быть ноль или более заказов в некоторой учетной системе,
при этом каждый заказ относится к какому-либо клиенту. Клиенты хранятся в
таблице client_tbl
, их заказы — в таблице order_tbl
.
На уровне SQL эту связь можно выразить так: на колонке client_id
в т. н.
дочерней таблице order_tbl
присутствует ограничение внешнего ключа,
ссылающееся на колонку id
в т. н. родительской таблице client_tbl
.
На уровне ORM такая связь представляется свойствами объектов. Экземпляр класса, соответствующего дочерней таблице, обычно имеет свойство-объектную ссылку, которая ссылается на родительский объект. С другой стороны такого отношения, экземпляр класса, соответствующий родительской таблице, может иметь свойство-коллекцию объектов (которая иногда называется "backref" - обратная ссылка), чтобы иметь возможность просмотреть все свои дочерние объекты.
Вначале определим схему данных, содержащую два класса Client
и Order
,
отображенные в две таблицы client_tbl
и order_tbl
. Отдельно опишем связь
между ними.
<schema>
<table name="client_tbl" sequence="client_seq" class="Client" xml-name="client">
<column name="id" type="longint">
<primary-key />
</column>
<column name="dt" type="datetime" null="false" default="sysdate" />
<column name="name" type="string" size="100" null="false" />
<column name="email" type="string" size="100" null="false" />
<column name="phone" type="string" size="50" null="true" />
<column name="budget" type="decimal" />
</table>
<table name="order_tbl" sequence="order_seq" class="Order" xml-name="order">
<column name="id" type="longint">
<primary-key />
</column>
<column name="client_id" type="longint" null="false">
<foreign-key table="client_tbl" key="id"/>
</column>
<column name="dt" type="datetime" null="false" default="sysdate" />
<column name="memo" type="string" size="100" />
<column name="total_sum" type="decimal" null="false" />
<column name="paid_sum" type="decimal" default="0" />
<column name="paid_dt" type="datetime" />
</table>
<relation type="one-to-many">
<one class="Client" property="orders" />
<many class="Order" property="owner" />
</relation>
</schema>
В описании таблицы order_tbl
указано, что колонка client_id
, является
внешним ключом, она играет роль ссылки. Когда между двумя таблицами существует
ровно одна подобная связь, то при описании связи с помощью элемента
<relation>
нет необходимости в дополнительных указаниях.
Код SQLite для приведенной выше схемы (использование утилиты yborm_gen
см. в
Tutorial1) будет выглядеть так:
CREATE TABLE client_tbl (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL,
phone VARCHAR(50),
budget NUMERIC
);
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 NOT NULL,
paid_sum NUMERIC DEFAULT 0,
paid_dt TIMESTAMP
, FOREIGN KEY (client_id) REFERENCES client_tbl(id)
);
Следующая программа использует доменные классы из приведенной выше схемы
(использование утилиты yborm_gen
см. в Tutorial1). Эта
программа сначала создаёт дочерний объект, присваивает какие-то значения его
полям, затем она создаёт родительский объект, потом она связывает дочерний
объект с родительским. В конце она сохраняет объекты в сессии и записывает их
в базу, завершая транзакцию (session.commit();
).
#include <iostream>
#include "domain/Client.h"
#include "domain/Order.h"
int main()
{
std::auto_ptr<Yb::SqlConnection> conn(new Yb::SqlConnection(
"sqlite+sqlite://./tut2.sqlite"));
Yb::Engine engine(Yb::Engine::READ_WRITE, conn);
Yb::Session session(Yb::init_schema(), &engine);
Domain::Order::Holder order;
std::string amount;
std::cout << "Enter order amount: \n";
std::cin >> amount;
order->total_sum = Yb::Decimal(amount);
Domain::Client::Holder client;
std::string name, email;
std::cout << "Enter name, email: \n";
std::cin >> name >> email;
client->name = name;
client->email = email;
client->dt = Yb::now();
std::cout << "Client's orders count: " << client->orders.size() << "\n";
order->owner = client;
std::cout << "Client's orders count: " << client->orders.size() << "\n";
order->save(session);
client->save(session);
session.commit();
std::cout << order->xmlize(1)->serialize() << std::endl;
return 0;
}
В этом примере видно, что связывание дочернего (класс Order
) объекта с
родительским (класс Client
) выглядит как присваивание (order->owner = client;
). Однако, этот пример оперирует с объектами доменных классов не
напрямую, как в Tutorial1, вместо этого используются
классы <DomainClass>::Holder
, которые необходимы для реализации вложенных и
рекурсивных объектных ссылок. То есть, все свойства-объектные ссылки
реализованы с использованием классов <DomainClass>::Holder
, а для их
разыменования нужно использовать стрелочку (->
), а не точку (.
).
Компилируем пример:
$ c++ -I. -I$YBORM_ROOT/include/yb -o tut2 tut2.cpp domain/Client.cpp domain/Order.cpp -L$YBORM_ROOT/lib -lybutil -lyborm
Запускаем на выполнение:
$ ./tut2
Enter order amount:
45.67
Enter name, email:
Petya [email protected]
Client's orders count: 0
Client's orders count: 1
<order><id>2</id><owner><id>2</id><dt>2014-06-01T17:25:32</dt><name>Petya</name><email>[email protected]</email><budget is_null="1"/></owner><dt>2014-06-01T17:25:17</dt><memo is_null="1"/><total-sum>45.67</total-sum><paid-sum>0</paid-sum><paid-dt is_null="1"/></order>
Если проследить операторы SQL, включив журнал сообщений по примеру Tutorial1, то увидим следующие подробности:
14-06-01 17:25:32.293 31927/31927 DEBG orm: flush started
14-06-01 17:25:32.293 31927/31927 DEBG sql: begin transaction
14-06-01 17:25:32.293 31927/31927 DEBG sql: prepare: INSERT INTO client_tbl (dt, name, email, budget) VALUES (?, ?, ?, ?)
14-06-01 17:25:32.294 31927/31927 DEBG sql: bind: (DateTime, String, String, Decimal)
14-06-01 17:25:32.294 31927/31927 DEBG sql: exec prepared: p1="'2014-06-01 17:25:32'" p2="'Petya'" p3="'[email protected]'" p4="NULL"
14-06-01 17:25:32.295 31927/31927 DEBG sql: prepare: SELECT SEQ LID FROM SQLITE_SEQUENCE WHERE NAME = 'client_tbl'
14-06-01 17:25:32.295 31927/31927 DEBG sql: exec prepared:
14-06-01 17:25:32.295 31927/31927 DEBG sql: fetch: LID='2'
14-06-01 17:25:32.295 31927/31927 DEBG sql: fetch: no more rows
14-06-01 17:25:32.295 31927/31927 DEBG sql: prepare: INSERT INTO order_tbl (client_id, dt, memo, total_sum, paid_sum, paid_dt) VALUES (?, ?, ?, ?, ?, ?)
14-06-01 17:25:32.295 31927/31927 DEBG sql: bind: (LongInt, DateTime, String, Decimal, Decimal, DateTime)
14-06-01 17:25:32.295 31927/31927 DEBG sql: exec prepared: p1="2" p2="'2014-06-01 17:25:17'" p3="NULL" p4="45.67" p5="0" p6="NULL"
14-06-01 17:25:32.295 31927/31927 DEBG sql: prepare: SELECT SEQ LID FROM SQLITE_SEQUENCE WHERE NAME = 'order_tbl'
14-06-01 17:25:32.296 31927/31927 DEBG sql: exec prepared:
14-06-01 17:25:32.296 31927/31927 DEBG sql: fetch: LID='2'
14-06-01 17:25:32.296 31927/31927 DEBG sql: fetch: no more rows
14-06-01 17:25:32.296 31927/31927 DEBG orm: flush finished OK
14-06-01 17:25:32.296 31927/31927 DEBG sql: commit
14-06-01 17:25:32.395 31927/31927 DEBG sql: prepare: 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.id = ?
14-06-01 17:25:32.395 31927/31927 DEBG sql: exec prepared: p1="2"
14-06-01 17:25:32.395 31927/31927 DEBG sql: fetch: ID='2' CLIENT_ID='2' DT='2014-06-01T17:25:17' MEMO=NULL TOTAL_SUM='45.67' RECEIPT_SUM='0' RECEIPT_DT=NULL
14-06-01 17:25:32.395 31927/31927 DEBG sql: fetch: no more rows
14-06-01 17:25:32.395 31927/31927 DEBG sql: prepare: SELECT client_tbl.id, client_tbl.dt, client_tbl.name, client_tbl.email, client_tbl.budget FROM client_tbl WHERE client_tbl.id = ?
14-06-01 17:25:32.396 31927/31927 DEBG sql: exec prepared: p1="2"
14-06-01 17:25:32.396 31927/31927 DEBG sql: fetch: ID='2' DT='2014-06-01T17:25:32' NAME='Petya' EMAIL='[email protected]' BUDGET=NULL
14-06-01 17:25:32.396 31927/31927 DEBG sql: fetch: no more rows
Обратите внимание, что объекты вставлены в корректной последовательности (первым — родительский, вторым — дочерний), так как используется топологическая сортировка графа зависимых объектов. Значение внешнего ключа будет присвоено автоматически, так же как и значения первичных ключей.
При связывании объектов с помощью присваивания происходит и изменение
коллекции orders
в соответствующем экземпляре класса Client
. Дело в том,
что оба свойства client->orders
и order->owner
используют один и тот же
внутренний объект RelationObject
. Можно добиться такого же эффекта вставкой
в свойство-коллекцию. Заменяем присваивание со стороны подчиненного объекта
order->owner = client;
на вставку в коллекцию со стороны главного объекта
client->orders.insert(*order);
Можно увидеть, что оба эти варианта абсолютно равносильны.