Пост про Django спровоцировал реакцию куда сильней, чем я думал. Видимо, животрепещущая тема! Поэтому про отношение Django с базой данных я решил тоже написать раньше, чем планировал.
ORM
Django общается с БД через ORM-интерфейс. То есть, программист не пишет напрямую SQL, а работает с как бы чисто объектным API. "Как бы" — это не то, что вы слышете через слово, когда общаетесь с нервничающим клерком, а именно "как бы". Если вы работаете с реляционной БД из объектной программы, то полностью абстрагироваться от нее невозможно (для педантов: "крайне сложно и непрактично") из-за пресловутого несовпадения "импеданса". То есть, вам все же лучше представлять, что ссылка на родительский объект лежит у ребенка в виде foreign key, а связь много-ко-многим организуется через дополнительную табличку. Просто, чтобы примерно понимать, почему узнать количество строк таблицы через select count()
быстрее, чем через len(get_object_list())
, который сначала потянет всю табличку из БД в приложение...
Тем не менее! Тем не менее, Django'вский ORM построен достаточно неплохо. Все начинается с описания классов объектов, которые потом превращаются в таблицы:
class Department(meta.Model):
title=meta.CharField(maxlength=50)
class Employee(meta.Model):
name=meta.CharField(maxlength=50)
birthday=meta.DateField(blank=True)
department=meta.ForeignKey(Department)
По идее, все довольно прозрачно (хотя все же не так красиво, как это выглядело бы в RoR). Об IDшках Django заботится самостоятельно.
Дальше в программе вы автомагически получаете модули со своими объектами, к которым можно обращаться примерно в таком духе:
from django.models.yourproject import departments, employees
d = departments.get_object(pk=id) # найти объект по ID
es = d.get_employee_list()
count = d.get_employee_count() # быстрый select count вместо len(es)
d.add_employee(name='Иваныч')
d.save()
Все, по идее, достаточно очевидно. Но это простые случаи. А вот синтаксис выполнения запросов с условиями уже слегка менее гладкий:
es = employees.get_list(
depatment__title__contains='планирования',
birthday.year < 1950
)
Через двойные подчеркивания (любовь к ним — родовая травма Питона) пишутся названия таблиц, полей и операторов. Выглядит страшновато. Кроме того, все переданные параметры объединяются через and
, а вот для того, чтобы сделать or
синтаксис гораздо страшнее!
Но что меня несказанно радует уже второй день, Django'вцы активно обсуждают, как это улучшить. Проходит полный пересмотр вся внутренняя механика определения таблиц, создания сложных запросов и еще нескольких шероховатых мест. Думаю, в ближайшую неделю они все решат, дела сейчас быстро продвигаются. Таким образом, весь тот код, что я написал выше, кроме крокодилообразного запроса, будет выглядеть практически так же. Так что, представление вы имеете.
Да.. Ну и кроме того, возможность работать напрямую запросами и курсорами через стандартный Питоновский DB API тоже есть.
Эффективность
В этом смысле тут тоже все получше, чем во многих реализациях. Как я уже упоминал, помимо методов получения списков объектов есть возможность запрашивать их количество через select count
. Кроме того, список объектов не обязательно закачивать целиком в память клиента, а получить в виде итератора и fetch'ить по одной записи за раз.
Есть интересная возможность автоматически подтянуть с объектом все его lookup'ы, и тогда при обращении к ним Django уже не будет обращаться к БД:
es = employees.get_list(select_related=True)
es[0].get_department() # обращения к БД не будет,
# department'ы уже подтянуты
А вот это, Денис, тебе понравится: вся работа с параметрами происходит только через bind-переменные :-). Не использовать их можно только если очень захотеть.
Еще одна очень правильная штука с точки зрения эффективности — это хранение больших объектов. Django не хранит их в BLOB'ах, а хранит в файлах на диске в заранее оговоренном месте. А значит попытка вытянуть список объектов, у которых есть файлы, не потянет за собой все эти файлы в память (привет Rails!).
Для работы с файлами есть отдельные функции, отдельные для каждого файлового поля. Например, если у нашего Employee появится поле picture с фотографией, то у него будут такие функции:
save_picture_file(filename, content)
— сохраняет реальное содержимое файла на диск, сама заботится о том, чтобы имя не совпало с существущимget_picture_filename()
— отдает реальное имя файлаget_picture_url()
— работает, если директория с файлами лежит в web-root'е, отдает полный URL к файлу
Ну и еще об удалении файлов при удалении записей Django тоже заботится.
Типы полей
Django имеет очень богатую систему типов. Несмотря на то, что все так или иначе на уровне БД сводится к строкам, числам и датам, на уровне приложения поддерживается куча всяких полезностей:
- CommaSeparatedIntegerField
- EMailField
- URLField
- FilePathField
- IPAddressField
- и т.д.
Поддержка их выливается прежде всего в уже написанные функции валидации значений, принятых с веба. Но кроме того, это влияет еще и на то, какие дефолтные элементы форм Django по ним генерировать. Например полю FilePathField задаются корневая директория на диске, маска файлов, и для него в HTML автоматически генерируется <select>
с именами файлов оттуда. Yummy!
Backend'ы
Для работы с различными СУБД Django опирается на питоновский DB API — стандартизованный интерфейс доступа к любой БД. Сам же Django в своем ORM заботится о специфических вещах вроде реализации "LIMIT" и экранирования имен полей и таблиц, чтобы вы могли использовать поле с именем "from", и чтобы не испытывали проблем от того, что в MSSQL идентификаторы экранируются квадратными скобками ([table]
), а не кавычками ("table"
).
Сейчас Django официально поддерживает 3 СУБД:
- PostgreSQL (с нее Django начиналось)
- MySQL
- SQLite
В процессе разработки два разных драйвера для MSSQL. Один, более доделанный, работает через ADO, а значит только на Windows. Другой — кроссплатформенный (через PyMSSQL), но его пока никто не видел. И еще в таком же довольно сыром состоянии поддержка Oracle.
Комментарии: 9
Слово "Yummy" пишется через "u" :)
Я посмотрел DB API, но не увидел там упоминаний об использовании своих способов работы с базой, в смысле использования процедур, вьюх и пр. Это както делается или как?
Имеется в виду питоновский DB API? Есть там и
callproc
, иexecute
, чтобы запросы выполнять.Или я не понял вопроса?
Всё хорошо написал, только забыл указать, что можно и SQL пользовать: other lookup options.
Ну не то, чтобы забыл... Просто решил не писать, отнеся это к продвинутым опциям, которые в общем обзоре можно и опустить. С этой штукой надо быть вообще осторожным, потому что ручным SQL'ом легко убить переносимость между backend'ами.
P.S. О! Все забывал написать, что у тебя блог за стилями на localhost ссылался, а вижу теперь - починил.
Одна из моих любимых фич Hibernate'а - это то, что он умеет считать количество подчиненных записей данного объекта не загружая подчиненных объектов:
order.getItems().size()
Вообще-то ничего особенного, но приятно!И еще: при разработке Hibernate девелоперы очень серьезно задумывались над возможностью его интеграции в унаследованные приложения и возможностью доступа к унаследованным БД. Как с этим у ORM Django?
Собственно и Django умеет их считать.
order.get_item_count()
. В той фразе я хотел показать, что про это просто надо знать..."Унаследованные" - это legacy? :-) Еле узнал слово в переводе. Django умеет с ними работать: http://www.djangoproject.com/documentation/legacy_databases/. Хотя, как с любой чужой структурой, все сильно зависит от того, насколько именно она "особенна".
И это счастье! Потому что для запросов вот такого рода:
ORM совершенно не подходит. На форумах Django вовсю обсуждается проблема агрегатных функций, хотя ясно, что полное их включение в ORM сделает его весьма тяжелым.
Отсюда вывод: выполнять элементарные запросы ORM прекрасно помогает. Но стоит задаче усложниться, он уже путается под ногами. Как автор справедливо заметил, многое выглядит страшненько. На последнем проекте я, сколько бы обращений к базе ни делал — каждый раз заглядывал в документацию ("ой, как же это было...")
Конечно, все сказанное относится не столько к Django, сколько к ORM вообще.
Ой, пример запроса дурацкий. Лучше так: