RuNTiME
|
|
« : 28-01-2012 15:57 » |
|
Предлагаю обсудить возможные пути реализации объектного представления таблиц в базе данных. Суть в том, чтобы создать объект с набором свойств, после задания/изменения которых, можно было сохранить его в БД. Количество свойств объекта может быть меньшим либо равным количеству полей в соответствующей таблице БД. Каждый объект и таблица БД в обязательном порядке имеют поле ID, которое будет использовано для изменения данных и для связей между объектами. Поясню на псевдокоде чего хотелось бы добиться: // базовая модель class Model { protected: int64 _id; public: bool Save(); };
class Book: public Model { public: Property name; // название книги Property autor; // автор книги Property year; // год издания };
// создаем новый объект книга Book book; book.name = "The C++ Programming Language"; book.autor = "Bjarne Stroustrup"; book.year = 2000;
// сохраняем объект в БД book.Save(); Итак класс Property должен представлять собой некоторую реализацию Variant. Метод Save() класса Book должен взять значения из свойств и сформировать SQL запрос для вставки/изменения записи в БД. Что касаясь загрузки объекта из БД, базовая модель должна иметь возможность загружать объекты по их ID. Так же очень желательно с возможностью указать набор полей, которые нужно загрузить.
|
|
|
Записан
|
Любимая игрушка - debugger ...
|
|
|
Finch
Спокойный
Администратор
Offline
Пол:
Пролетал мимо
|
|
« Ответ #1 : 28-01-2012 17:11 » |
|
1) id записи не обязательное, хоть и желательное поле 2) В базах данных есть понятие "обязательные поля" При вставке новых записей в таблицу, без упоминания этих полей, ведет к ошибке. 3) Это все задуманно в качестве изобретения велосипеда?
|
|
|
Записан
|
Не будите спашяго дракона. Джаффар (Коша)
|
|
|
RuNTiME
|
|
« Ответ #2 : 28-01-2012 17:22 » |
|
Finch, 1) id обязательное поле только в данном случае, оно необходимо для правильного функционирования объектов, т.к. должно быть заранее определенное поле для любого объекта, по которому можно однозначно его идентифицировать (первичный ключ). 2) можно реализовать проверку в объекте на заполнение обязательных полей и например генерировать исключение в случае их незаполнения. 3) велосипед - да согласен, например для языка Python ( https://docs.djangoproject.com/en/dev/topics/db/models/) , но реализацию чего - то подобного под C++ я не видел, впрочем может плохо искал... И прекрасно понимаю, что в следствии "статичности" C++ провернуть такое довольно трудно. Собственно по этому и хочу обсудить возможности реализации на C++ подобного подхода построения модели данных и её плюсы и минусы.
|
|
|
Записан
|
Любимая игрушка - debugger ...
|
|
|
Dale
|
|
« Ответ #3 : 28-01-2012 17:37 » |
|
Эта идея имеет какие-то принципиальные отличия от обычного ORM?
|
|
|
Записан
|
Всего лишь неделя кодирования с последующей неделей отладки могут сэкономить целый час, потраченный на планирование программы. - Дж. Коплин.
Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard
Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
|
|
|
RuNTiME
|
|
« Ответ #4 : 28-01-2012 17:41 » |
|
Dale, ORM - именно то что мне нужно
|
|
|
Записан
|
Любимая игрушка - debugger ...
|
|
|
Finch
Спокойный
Администратор
Offline
Пол:
Пролетал мимо
|
|
« Ответ #5 : 28-01-2012 17:42 » |
|
Ну Qt как-то решали данную проблему В библиотеке есть набор классов для работы с базами данных. Можно начать с изучения "как они это сделали"
|
|
|
Записан
|
Не будите спашяго дракона. Джаффар (Коша)
|
|
|
Dale
|
|
« Ответ #6 : 28-01-2012 17:59 » |
|
Dale, ORM - именно то что мне нужно Тогда следующий вопрос: какие существенные преимущества предложенная идея имеет перед многочисленными имеющимися реализациями ORM? Одно только предложение использовать единственный тип Property для всех полей, на мой взгляд, способно порушить всю целостность данных. Ведь у каждого поля должен быть собственный реляционный домен. А в данном случае никто не мешает с таким же успехом написать: book.autor = 2000; book.year = "Bjarne Stroustrup"; Если, конечно, сам тип Property не будет содержать в себе информацию о домене и проверять совместимость типов на этапе выполнения. Но это такая громоздкая конструкция в результате получится...
|
|
|
Записан
|
Всего лишь неделя кодирования с последующей неделей отладки могут сэкономить целый час, потраченный на планирование программы. - Дж. Коплин.
Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard
Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
|
|
|
RuNTiME
|
|
« Ответ #7 : 28-01-2012 18:11 » |
|
Finch, В Qt используется собственный препроцессор расширяющий возможности языка C++. Dale, Тогда следующий вопрос: какие существенные преимущества предложенная идея имеет перед многочисленными имеющимися реализациями ORM? Думаю в том что нужна реализация именно на C++, а таковой в готовом виде я не видел Одно только предложение использовать единственный тип Property для всех полей, на мой взгляд, способно порушить всю целостность данных. Верно подмечено. Сам думал над этим. Можно отказаться от Variant и использовать встроенную проверку типов C++. class Book: public Model { public: StringProperty name; // название книги StringProperty autor; // автор книги IntProperty year; // год издания };
|
|
|
Записан
|
Любимая игрушка - debugger ...
|
|
|
Dale
|
|
« Ответ #8 : 28-01-2012 18:21 » |
|
Думаю в том что нужна реализация именно на C++, а таковой в готовом виде я не видел Сам лично на этот счет ничего посоветовать не могу, ибо давным-давно завязал с попытками работать с данными на C++, это далеко не лучший язык для такого применения. Википедия предлагает несколько вариантов: http://en.wikipedia.org/wiki/List_of_object-relational_mapping_software#C.2B.2B . Думаю, при должном усердии найдется на порядок больше готовых решений, поскольку тема ORM не вчера возникла, задача довольно типовая.
|
|
|
Записан
|
Всего лишь неделя кодирования с последующей неделей отладки могут сэкономить целый час, потраченный на планирование программы. - Дж. Коплин.
Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard
Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
|
|
|
Finch
Спокойный
Администратор
Offline
Пол:
Пролетал мимо
|
|
« Ответ #9 : 28-01-2012 18:59 » |
|
RuNTiME, В Qt в основном препроцессор обрабатывает понятие сигналов и слотов. Ну и формочки из ui формирует. Большего я не видел в .moc файлах. Ну если хочеш поизобретать велосипед. Чтож начнем В любой базе данных ограниченное количество базовых типов. Можно на пальцах пересчитать. Делаем базовый тип, от него наследуем описание всех остальных. Пример BaseType -> IntType BaseType -> StringType BaseType -> DateTimeType Теперь класс Variant будет хранилешем BaseType и фабрикой классов. Класс Record будет соответсвовать записи в базе данных. Хранить данные будет в std::map<string, Variant> Работа осушествляется примерно так: Record rec; // Тут как-то загоняем запись rec["mail"] = "My mail"; Если все таки мы сделаем rec["mail"] = 2000; То должен выкинутся Exception. Так как StringType знает, что он получает данные только с FromString.
|
|
« Последнее редактирование: 28-01-2012 19:06 от Finch »
|
Записан
|
Не будите спашяго дракона. Джаффар (Коша)
|
|
|
RuNTiME
|
|
« Ответ #10 : 28-01-2012 19:42 » |
|
Finch, еще думаю BaseType должен содержать свойство is_modified, чтобы знать какие поля были изменены и при обновлении данных в запрос включать только измененные. P.S. Посмотрел на готовую реализацию ORM на C++ - LiteSQL, в принципе понравилось, но довольно таки громоздко Вот с таких мыслей и начинается изобретение велосипедов... Пожалуй попробую чего - нибудь написать....
|
|
|
Записан
|
Любимая игрушка - debugger ...
|
|
|
Finch
Спокойный
Администратор
Offline
Пол:
Пролетал мимо
|
|
« Ответ #11 : 28-01-2012 20:26 » |
|
Это уже тонкости реализации
|
|
|
Записан
|
Не будите спашяго дракона. Джаффар (Коша)
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #12 : 29-01-2012 07:33 » |
|
Если я правильно понял, в схеме, описанной автором, Property по сути играет роль атрибута, помечающего дублируемые в БД поля.
Предлагаю отделить мух от котлет, и по аналогии с MVC сделать Model-DataController пары. Классы с данными (Model) оставить очищенными от всяких Save и т.п. Однако в каждом таком классе нужно установить friend-доступ для другого (DataController) класса и описать события обновления данных или что-нибудь в этом духе, если требуется автоматическое срабатывание DataController на событие.
Внутри DataController, во-первых, завести коллекцию с обрабатываемыми в БД полями. Каждое поле может быть описано отдельным объектом, хранящим информацию об отображении подмножества связанных полей модели в подмножество связанных полей БД, правила преобразования данных (если нужно), соответствия типов. Во-вторых, реализовать select, insert, update и delete запросы по коллекции полей с известным ключом - это несложная задача. В-третьих, реализовать фабрику Model-объектов, когда при создании нового объекта (в памяти), на его события автоматически подписывается DataController, и происходит загрузка данных из БД в поля, после чего объект поступает в работу.
Один объект DataController может обслуживать много объектов Model одного класса. В параллельных потоках нужна весьма аккуратная реализация DataController - то, что называется thread safe. При использовании событий тоже нужно аккуратно отработать сохранения и загрузки данных, чтобы каскадные операции не замыкались в цикл, и чтобы любой чих не приводил к полной загрузке/выгрузке БД в/из память.
Для множества классов моделей будет множество соответствующих классов DataController - как правило 1 к 1. Но в DataController при помощи templates можно написать достаточно богатый класс-предок, чтобы его потомки, настраиваемые на конкретные классы Model, были простыми и чисто конфигурационными. Формирование кода настроечных классов тоже можно упростить при помощи макросов, сведя ручное написание к минимуму.
Ещё одна интересная вещь - это классы-ссылки. В их объектах можно хранить указатель на целевой объект, а также ID целевого объекта. Если указатель NULL, при обращении автоматически загружать объект из БД. Будет ленивая загрузка объектов в память - по мере надобности. По аналогии со сборщиком мусора можно реализовать стратегию очистки памяти за счёт сброса старых, давно неиспользуемых объектов в БД. Выйдет система, где операции с БД происходят в "фоновом" режиме без вмешательства программиста.
Однако все такие вещи писать, а главное, отлаживать - довольно трудоёмкое занятие на любителей. Всё же лучше использовать готовые библиотеки, подобрав под свои нужды наиболее подходящую.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
RuNTiME
|
|
« Ответ #13 : 29-01-2012 14:59 » |
|
Dimka, 1) Как я понял DataController представляет собой коллекцию объектов Model и каждый объект Model содержит указатель на коллекцию, но тогда мне не понятно: Внутри DataController, во-первых, завести коллекцию с обрабатываемыми в БД полями Какие поля в коллекции, если поля находятся в классах Model? 2) Каждое поле может быть описано отдельным объектом, хранящим информацию об отображении подмножества связанных полей модели в подмножество связанных полей БД Либо я что - то не понял, либо тут говорится об объектах Model хранящих наборы полей, отображаемых на поля таблиц в БД? И по сути получается, что каждый класс Model отображается на свою таблицу в БД. 3) Во-вторых, реализовать select, insert, update и delete запросы по коллекции полей с известным ключом - это несложная задача. Хорошая мысль перенести Save непосредственно в коллекцию, но как быть с крупномасштабными изменениями коллекции, например нужно изменить 10000 записей? Хранить все изменения в памяти пока в конце цикла изменений не будет вызван метод Save для коллекции? Есть конечно вариант установить ограничение используемой памяти и при превышении лимита "сбрасывать" объекты в БД, но все же.... 4) В-третьих, реализовать фабрику Model-объектов, когда при создании нового объекта (в памяти), на его события автоматически подписывается DataController, и происходит загрузка данных из БД в поля, после чего объект поступает в работу. Как быть в случае, если нужно загрузить всего 2 из 10 полей объекта, а в другом все 10? 5) Для множества классов моделей будет множество соответствующих классов DataController Если каждый объект Model соответствует своей таблице в БД, а DataController представляет собой коллекцию объектов Model, то получается объект DataController соответствует одной базе данных, а несколько объектов DataController будут уже представлять набор баз данных... по - моему это уже перебор Всё же лучше использовать готовые библиотеки, подобрав под свои нужды наиболее подходящую. Это лучше, только в том случае, когда поджимают сроки, начальство и т.д. и т.п. Но когда время есть и пишется это для себя, лучше разработать собственное решение и пусть оно будет далеко от совершенства и возможно даже просто примитивно, но позволит значительно повысить свой уровень знаний. Ведь использовать API готовой библиотеки может любой
|
|
|
Записан
|
Любимая игрушка - debugger ...
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #14 : 29-01-2012 17:49 » |
|
Как я понял DataController представляет собой коллекцию объектов Model В корне не так. DataController представляет собой наблюдателя и посетителя, который берёт какой-нибудь объект Model и совершает над ним действия по обмену данными между Model и БД. Model про БД ничего не знает. Максимум, что есть у Model - это поле ID для нужд БД, и средства оповещения наблюдателей об изменениях данных. Какие поля в коллекции, если поля находятся в классах Model? Правильно, поля с данными находятся в Model. В БД могут быть немного (или много) другие поля. DataController знает, какие поля Model каким полям в БД соответствуют, и как соответствуют, какие нужно делать преобразования. Чтобы эту информацию хранить, в DataController есть коллекции полей и правил преобразования. Вот в Model что-то поменялось, Model сигнализирует об изменениях своим наблюдателям. По полученному сигналу DataController заходит посетителем в Model, берёт из своей коллекции списки интересующих его полей, смотрит - пусть, поле X его интересует. Тогда он читает из Model значение поля X и что-то с ним делает по своим правилам, куда-то что-то пишет. Хорошая мысль перенести Save непосредственно в коллекцию, но как быть с крупномасштабными изменениями коллекции, например нужно изменить 10000 записей? Мысль может и хорошая, но не моя Как быть в случае, если нужно загрузить всего 2 из 10 полей объекта, а в другом все 10? Вот для этого и нужны DataController.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
RuNTiME
|
|
« Ответ #15 : 29-01-2012 19:11 » |
|
DataController знает, какие поля Model каким полям в БД соответствуют, и как соответствуют, какие нужно делать преобразования. Чтобы эту информацию хранить, в DataController есть коллекции полей и правил преобразования. Но в таком случае DataController выступает в роли одной глобальной модели, в которой сосредоточена вся логика по работе с БД, а классы Model уже фактически не являются моделями. Классы Model выступают в виде простых структур для хранения данных. Не лучше ли будет перенести логику преобразования и проверки данных в классы Model, а коллекция DataController уже будет забирать готовенькие данные из Model? К тому же в таком случае не придется хранить на мой взгляд довольно объемные правила преобразования данных в коллекции. Каким образом коллекция должна узнать, что конкретный объект Model принадлежит конкретной таблице БД? Это означает, что информацию о принадлежности Model к таблице нужно хранить именно в классе Model, а коллекция должна уметь читать эту информацию. В таком случае Model уже начинает кое - что знать о структуре БД. Да и на счет мысли о фоновом режиме работы с БД. Каким образом тогда реализовать работу с транзакциями, если невозможно будет точно сказать попали данные в БД или нет? Представим такую ситуацию, приложение запустило транзакцию, произвело некоторые действие с данными удалило/изменило/добавило. Часть этих данных были выгружены в БД в фоновом режиме, а часть осталась ждать в памяти и тут возникает исключение, программа откатывает транзакцию и откатывается только та часть, которая была выгружена. А что сделается с тем что было в памяти можно только догадываться.... Получается чтобы корректно отрабатывались транзакции, половину механизма транзакций придется перенести в DataController, а это уже выходит далеко за рамки модели данных... Вот для этого и нужны DataController. Мне кажется набор полей, которые нужно загрузить должно задавать основное приложение, которое работает с моделью данных. Например при запросе объекта Model из коллекции, а модель данных должна позволять загружать произвольное количество полей из набора содержащегося в конкретном объекте Model.
|
|
|
Записан
|
Любимая игрушка - debugger ...
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #16 : 29-01-2012 19:25 » |
|
Но в таком случае DataController выступает в роли одной глобальной модели, в которой сосредоточена вся логика по работе с БД, а классы Model уже фактически не являются моделями. Классы Model выступают в виде простых структур для хранения данных. Я этого не понимаю. Model - по определению объект предметной области. Т.е. структура любой сложности с методами. Единственное, что в общем случае про неё точно можно сказать - она не знает и знать не должна про существование БД, GUI и т.п. вещей, к предметной области не относящихся. Если это не так, то всё преимущество архитектур типа MVC теряется. Не лучше ли будет перенести логику преобразования и проверки данных в классы Model, а коллекция DataController уже будет забирать готовенькие данные из Model? Опять же, нужно различать преобразования и проверки предметной области и БД-специфичные. И ещё: модель данных и модель предметной области - это разные вещи. Если у тебя первична модель данных, а классы в программе ты собираешься жёстко подчинять существующим в модели данных таблицам, выйдет неудобная в использовании конструкция.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
RuNTiME
|
|
« Ответ #17 : 29-01-2012 20:00 » |
|
Model - по определению объект предметной области. Да в общем случае это так. Но например ORM реализованный в django (выше есть ссылка). В моделях используется смешанный подход: задается структура таблицы (БД специфичная модель) и реализуется модель предметной области. При этом не нарушаются законы MVC. Есть и другой подход реализованный в LiteSQL http://sourceforge.net/apps/trac/litesql/wiki/QuickStart на основе описания структуры БД и связей создается модель данных в которой каждый объект Model соответствует своей таблице и выполняет БД специфичные проверки и преобразования. Такой подход мне и хотелось бы использовать. В таком случае получается что объекты Model и DataController относятся именно к модели данных. А уже в свою очередь часть классов приложения, работающих с объектами Model будут относится к модели предметной области, а View и Controller уже в свою очередь работать с ними. Говоря о проверке и преобразовании данных в объектах Model я имел ввиду БД специфичные проверки. Такой подход упрощает реализацию модели данных. PS: И все же пока не ясно, каким образом коллекция должна узнавать к какой таблице принадлежит конкретный объект Model.
|
|
|
Записан
|
Любимая игрушка - debugger ...
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #18 : 30-01-2012 05:06 » |
|
И все же пока не ясно, каким образом коллекция должна узнавать к какой таблице принадлежит конкретный объект Model. Не знаю, это ты выдумал коллекцию Я говорил лишь о коллекции полей. А так каждый класс соответствует таблице, следовательно, информация о принадлежности жёстко определена на уровне компиляции.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
|