![]() |
Модераторы: LSD, AntonSaburov |
![]() ![]() ![]() |
|
chinook |
|
|||
Unregistered |
Привет!
Есть приложение, которое получает данные из базы. Вариантов запросов много, запросы сложные. Код - отстой. Конструирование запросов разбросано по сотне классов, каждый сам для себя. Грядет переписывание этой кучи мусора, поэтому я хотел бы локализовать создание query в одном месте. Для меня очевидно (:-)), что такой объект = composit + interpreter, мы в произвольном порядке загоняем туда условия, потом получаем строку, представляющую из себя query. Я, собственно, уже продумал структуру и все такое. Но! Во-первых, не хочется изобретать велосипед, возможно, все до меня уже сделали. Во-вторых, возможно, это не самый лучший путь, и кто-то знает лучший. Собственно, вопросы: кто-нибудь про такой подход слышал? кто-нибудь с такой проблеммой сталкивался? Заранее благодарен! |
|||
|
||||
Stampede |
|
||||
![]() Гносеолог ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 963 Регистрация: 25.4.2005 Где: Calgary, Alberta, Canada Репутация: 66 Всего: 144 |
Слышать не слышать, но сталкиваться - сталкивался. И более того, реализовывал. Разумеется, задача не совсем тривиальная, и с наскоку ее не решишь. Я. в частности, поступил следующим образом. Я создал фреймворк из абстрактных понятий:
Описатели аттрибутов не зашиты в код и хранятся отдельно (в базе, в XML, в проперях - неважно). Описатель содержит такие сведения: имя таблицы/столбца, тип данных, имя атрибута и плюс еще всякие флаги. Value - это иерархия типизированных описателей значений, которые знают, как представлять себя в базе и на экране. Expression - это собственно условие поиска, которое подразделяется на логические условия и атомарные условия. Таким образом можно строить запросы неограниченной вложенности. Query - это надстройка над Expression, которая может включать дополнительную информацию о порядке сортировки, диапазоне поиска и пр. Важный момент: в этих интерфейсах нет ни намека на SQL, потому что клиенту об этом знать совершенно необязательно. На сервере эти интерфейсы расширены до SqlExpression и SqlQuery, соответственно, и у них есть методы, специфические для пострения собственно выражений SQL. Вот в общих словах и весь подход. У меня - работает. Да, там есть еще один интересный момент, связанный с поддержкой разных типов СУБД. Там фишка в том, что выносить конструкции SQL в ресурсы очень неудобно, потому что при написании/отладке хорошо видеть, что именно у тебя происходит. Поэтому тексты шаблонов у меня зашиты прямо в сорцах. А чтобы иметь возможность менять синтаксис в зависимости от СУБД, я создал базовый класс Flavored, и все прочие классы, которые содержат код SQL, унаследованы от него. У этого класса есть два метода , setFlavor(String flavor) и getFlavored(String str). Первый используется при инициализации класса, чтобы установить версию, например, "postgres". А вот когда приходит время собственно генерировать запрос, то используется примерно такой код:
А внутри происходит следующее: когда вызывается getFlavored(), он использует рефлексию и ищет имя переменной, которая соотвествует переданной строке. В данном случае это переменная sortClause. После этого он присоединяет к имени переменной ранее установленный тип базы (postgres) и ищет переменную sortClausePostgres. Если таковая находится, он возвращает ее значение, если нет - то возвращает исходную строку. Метод довольно зыбкий, потому что тут получается очень жесткий контракт, который по незнанию или забывчивости легко нарушить, и никто на этапе компиляции даже не пикнет, но если его придерживаться, то получается весьма удобно. ЗЫ. А ты чего до сих пор не зарегистрируешься? Шестым чувством чувствую, что мы с тобой земляки ![]() |
||||
|
|||||
Chinook |
|
|||
Новичок Профиль Группа: Участник Сообщений: 8 Регистрация: 21.5.2005 Где: Vancouver, BC, Ca nada Репутация: нет Всего: нет |
Спасибо за подробный ответ!
Круто и все такое! У нас планируется нечто типа того, но это у нас называется metadata. Дело в том, что в зависимости от выбора основного источника данных, связанные данные могут находится в совершенно различных таблицах с различной структурой. В результате, после обращения к классам, работающих с metadata, мы получаем таблицы и список полей, по которым они связаны. А вот дальше надо составить собственно запрос. Пока это решается компонованием строки в зависимости от условий (таблиц, подзапросов, полей для связывания и т.п.). Я не собиралься замахиваться на такого монстра, как у тебя :-) Забумывалось написать класс, не привязанный к определенной структуре данных, который может принимать: 1. Поля для select 2. таблицы для from 3. поля для where с условием связывания (=, *=, >...) и условием обязательности (or and) 4. поля для having 5. поля для order by При этом 1, 2, 3 могут быть тем же классом (т.е. вложенные подзапросы - у нас Oracle, который это понимает). Преимущества такого подхода: 1. все построение запросов в одном месте 2. нет определенного порядка добавления элементов (а то в полупостроенном запросе приходится вставлять новый подзапрос в группу from - выглядит ужасно) 3. Код бизнес-классов не захламляется всякими строками с кусками запроса (ну, это производное). 4. этот класс (скорее - пакет) можно будет использовать к любом приложении (по крайней мере с ораклом) Почему, собственно, кажется это целесообразным: 1. interpter: шаблон для перевода чего-либо с одного языка на другой. Т.е. перевод на sql - сам Бог велел. 2. composit: обход дерева, в которое превращается запрос - все точно по документации. Просто посроение такого класса(пакета) мне кажется очевидным, если начинать что-то новое, заранее зная про большое количество разнородных запросов. Поэтому я и спросил - неверняка такой пакет уже написан, и, учитывая большое количество исходников в и-нете, мог попасться кому-то на глаза(на мои не попался). Я, конечно, танцевал на этих граблях и раньше много раз, но это были уже работающие системы и, мысли о рефакторинге всего кода вызывал ужасные ночные галлюцинации, поэтому руки так и не дошли.
Зарегистрировался. Шестым!?!?! Ну, возможно :-) Я вообще-то из Воронежа, живу в Ванкувере. Добавлено @ 01:54 О, блин, Калгари! А я что-то затормозил :-) Ну да, точно, чинук - это, слава Богу, у вас :-) |
|||
|
||||
tux |
|
|||
![]() Летатель ![]() ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 1853 Регистрация: 10.2.2005 Где: msk.ru Репутация: 74 Всего: 132 |
Может быть еще вот эта статья Скотта Эмблера (из Торонто!!!
![]() |
|||
|
||||
batigoal |
|
|||
![]() Нелетучий Мыш ![]() ![]() ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 6423 Регистрация: 28.12.2004 Где: Санктъ-Петербургъ Репутация: 16 Всего: 151 |
tux
Спасибо за линк. Обязательно прочту. -------------------- "Чтобы правильно задать вопрос, нужно знать большую часть ответа" (Р. Шекли) ЖоржЖЖ |
|||
|
||||
maximb |
|
|||
![]() Новичок Профиль Группа: Awaiting Authorisation Сообщений: 48 Регистрация: 23.5.2005 Где: Украина, г.Симфер ополь Репутация: 2 Всего: 6 |
А может стоит использовать Hibernate
Зачем изобретать велосипед |
|||
|
||||
batigoal |
|
|||
![]() Нелетучий Мыш ![]() ![]() ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 6423 Регистрация: 28.12.2004 Где: Санктъ-Петербургъ Репутация: 16 Всего: 151 |
Может, и стоит, просто я его пока не знаю
![]() -------------------- "Чтобы правильно задать вопрос, нужно знать большую часть ответа" (Р. Шекли) ЖоржЖЖ |
|||
|
||||
maximb |
|
|||
![]() Новичок Профиль Группа: Awaiting Authorisation Сообщений: 48 Регистрация: 23.5.2005 Где: Украина, г.Симфер ополь Репутация: 2 Всего: 6 |
Да там не так сложно, главное научиться нормально писать маппинги на классы и все.
Да и еще, небольшое дополнение: Для обеспечения нормальных транзакций и упрощения работы с транзакциями очень хорошо использовать Spring в одной связке с Hibernate. |
|||
|
||||
tux |
|
|||
![]() Летатель ![]() ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 1853 Регистрация: 10.2.2005 Где: msk.ru Репутация: 74 Всего: 132 |
В данном случае это приведет к необходимости создания слоя хранимых объектов и преобразования запросов из SQL в объектные запросы на HQL. Не уверен, что преобразование всегда можно будет выполнить один в один. В частности это зависит от того, в каком объеме используются функции, характерные только для данной используемой СУБД. Хотя генератор запросов на HQL в Hibernate уже есть и если запросы не очень сложные и их не очень много возможно что действительно использование Hibernate оправдано.
|
|||
|
||||
maximb |
|
|||
![]() Новичок Профиль Группа: Awaiting Authorisation Сообщений: 48 Регистрация: 23.5.2005 Где: Украина, г.Симфер ополь Репутация: 2 Всего: 6 |
Если очень надо могу помочь примером "Spring + Hibernate"
Но придется денек подождать т.к. мне его надо будет сделать. То, что у меня уже есть к сожалению дать не могу т.к. это текущий проект. Добавлено @ 11:07
Да но это избавляет программиста от того, чтобы делать преобразования запросов для разных БД вручную, вся ответственность ложится на Hibernate. Насчет сложности запросов, то средствами Hibernate можно делать запросы любой сложности. |
|||
|
||||
Domestic Cat |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Экс. модератор Сообщений: 5452 Регистрация: 3.5.2004 Где: Dallas, US Репутация: 13 Всего: 172 |
![]() ![]() ![]() -------------------- |
|||
|
||||
maximb |
|
|||
![]() Новичок Профиль Группа: Awaiting Authorisation Сообщений: 48 Регистрация: 23.5.2005 Где: Украина, г.Симфер ополь Репутация: 2 Всего: 6 |
Domestic Cat
![]() Сорри, сразу не увидел но обсуждение Hibernate ведется еще и в этом топике http://forum.vingrad.ru/index.php?showtopic=31813 |
|||
|
||||
3,14 |
|
|||
![]() Эксперт ![]() ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 1614 Регистрация: 18.6.2004 Где: Н. Новгород Репутация: 3 Всего: 24 |
ИМХО использовать Hibernate и если есть возможность все запросы вынести на сторону БД
-------------------- Может быть, это только мой бред, Может быть, жизнь не так хороша, Может быть, я не выйду на свет, Но я летал, когда пела душа... |
|||
|
||||
tux |
|
|||
![]() Летатель ![]() ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 1853 Регистрация: 10.2.2005 Где: msk.ru Репутация: 74 Всего: 132 |
Язык запросов, сглаживающий различия между различными СУБД, по определению должен обладать меньшими возможностями, чем язык запросов СУБД. Хотя, не спорю, в подавляющем большинстве случаев его вполне достаточно. |
|||
|
||||
maximb |
|
|||
![]() Новичок Профиль Группа: Awaiting Authorisation Сообщений: 48 Регистрация: 23.5.2005 Где: Украина, г.Симфер ополь Репутация: 2 Всего: 6 |
За универсальность надо платить ![]() Понятно, что собственная реализация работы с БД (если все правильно сделано) может быть лучше чем универсальный подход, но в этом случае не надо забывать сколько времени и сил уйдет на эту собственную реализацию. + Суппорт, стоит внести небольшие изменения в модель данных или перейти на другую БД как придется переписывать массу запросов, что в Хибернейте решается простым переписыванием маппингов (и то это придется делать только в том случае если произошли изменения в модели) |
|||
|
||||
Sleepy_PIP |
|
||||
Опытный ![]() ![]() Профиль Группа: Участник Сообщений: 512 Регистрация: 30.6.2004 Где: Moscow Репутация: 2 Всего: 12 |
не ... много слышал о Гибернейте, пока не читал така ка не надо. Работаю с приложением - самостоятельно строящим SQL запросы. НО! простая штачка, (а таких много-много еще) - какую табличку ставить во from первой, а какие потом - ни один построитель запросов вам НЕ ДАСТ! А от этого время выполнения запроса может отличаться в 10, 100, 1000 и более раз. Ну и что теперь? -------------------- -- Sleepy_PIP. Pavel Pryazhentsev (ex. 2:5020/141) "... Лучше быть нужным, чем свободным ..." |
||||
|
|||||
Stampede |
|
|||
![]() Гносеолог ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 963 Регистрация: 25.4.2005 Где: Calgary, Alberta, Canada Репутация: 66 Всего: 144 |
Не верю! (с) Ни один вменяемый оптимизатор не будет строить план выполнения запроса, основываясь на порядке перечисления таблиц во from предложении. Если действительно имело место расхождение "в 10, 100, 1000 и более раз", то очень хотелось бы узнать имя и версию СУБД, и по возможности увидеть планы запросов для разных вариантов перечисления таблиц. Если это была реальная промышленная СУБД, то единственное предположение, которое приходит в голову, это что у оптимизатора не было достаточно данных для выбора лучшей стратегии, чем ориентироваться на порядок таблиц в запросе (например, не было создано подходящего индекса или не была построена статистика). |
|||
|
||||
Chinook |
|
|||
Новичок Профиль Группа: Участник Сообщений: 8 Регистрация: 21.5.2005 Где: Vancouver, BC, Ca nada Репутация: нет Всего: нет |
Я немного знаком с Hibernate и даже использую его в одном из проектов, но не очень активно и не могу назвать себя специалистом, поэтому хотел бы уточнить кое-что. Проблема, которую необходимо решить, состоит в том, что в момент написания программы я не знаю, какие таблицы и view(из известного набора в сотни таблиц и тысячь view) будут использованы в запросе. В принципе, mapping для такого большого количества объектов сделать можно. А как дальше его использовать? Т.е. у меня есть mappping и соответствующие объекты для всех таблиц и view, в результате установок параметров формы пользователем, я получаю несколько десятков параметров, по которым мне нужно построить запрос. Теперь я уже знаю, какие таблицы и поля я должен использовать, мне необходимо составить лишь запрос (строку, например) к базе данных. Запрос может быть совсем простым (на несколько строк), а можем быть более сложным - на десяток печатных страниц. Вложенные подзапросы используются очень активно, хранимые процедуры и функции не используются вообще. База - Oracle, поддержка дрегих баз не требуется. Спасибо. |
|||
|
||||
Chinook |
|
|||
Новичок Профиль Группа: Участник Сообщений: 8 Регистрация: 21.5.2005 Где: Vancouver, BC, Ca nada Репутация: нет Всего: нет |
Кстати, Oracle работает быстрее, если использовать конструкцию в блоке where: значение = переменная (100 = id) вместо переменная = значение (id = 100) Но не в сотни раз ![]() |
|||
|
||||
batigoal |
|
|||
![]() Нелетучий Мыш ![]() ![]() ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 6423 Регистрация: 28.12.2004 Где: Санктъ-Петербургъ Репутация: 16 Всего: 151 |
Странно. А почему?
-------------------- "Чтобы правильно задать вопрос, нужно знать большую часть ответа" (Р. Шекли) ЖоржЖЖ |
|||
|
||||
Stampede |
|
|||
![]() Гносеолог ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 963 Регистрация: 25.4.2005 Где: Calgary, Alberta, Canada Репутация: 66 Всего: 144 |
Дык а план, план-то чего говорит? Дело в том, что оптимизатор - это недетерминированная машина. На одной и той же базе, для одних и тех же запросов, но при разных объемах данных могут быть сгенерированы совершенно разные планы выполнения. Поэтому я бы не стал так уж категорически утверждать, что такой-то вариант работает быстрее. Не исключено, что при других условиях быстрее будет как раз наоборот. Поэтому при любых странностях - сразу смотреть в план ![]() |
|||
|
||||
Sleepy_PIP |
|
||||
Опытный ![]() ![]() Профиль Группа: Участник Сообщений: 512 Регистрация: 30.6.2004 Где: Moscow Репутация: 2 Всего: 12 |
вот вот вот! уже ближе к истине. и на новой работе я с этим столкнулся в плотную. НИ один оптимизатор (даже если вы натравите статистику) - не сработает лучше грамотного SQL спеца. Это есть так и долго будет еще так. А что уж тут говорить про построители запросос типа Хибернете, которые нифига о БД не знают! ... Ну либо писатели не раскрыли сути процессов о том, как строить запросы к реальной БД. В общем случае - Хибернейт - работает. Но упаси Боже это использовать на продакшене с реальными данными в несколько хотя-б гигов (а ИМХо может и меньше). ИМХО онли!. -------------------- -- Sleepy_PIP. Pavel Pryazhentsev (ex. 2:5020/141) "... Лучше быть нужным, чем свободным ..." |
||||
|
|||||
Stampede |
|
|||
![]() Гносеолог ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 963 Регистрация: 25.4.2005 Где: Calgary, Alberta, Canada Репутация: 66 Всего: 144 |
Я бы подкорректировал данное утверждение. Дело на самом деле отнюдь не только и не столько в размере базы, а по большей части - в геометрии рук ДБ разработчика ![]() Во многих больших и даже очень больших (в плане объема) OLTP системах большинство операций на самом деле совершенно тривиальны: снять с одного счета, записать на другой, подбить бабки в разных измерениях - фсе! Тогда практически любой запрос сводится к извлечению нескольких записей по первичным ключам, двух- трехсторонним джойнам, выборкам из пре-аггрегированных таблиц. Это для любой СУБД сущая чепуха, пусть там даже будет хоть миллиард записей. Неприятности обычно происходят в системах с кривым дизайном базы, например когда к одним и тем же данным пытаются лезть еще и с OLAP запросами. А это бывает в тех случаях, когда на этапе проектирования базы не было осуществлено явного разделения на операционное и архивное пространство, и/или не было предусмотрено мер пре-агрегирования. Типичный пример такого близорукого дизайна - это когда чтобы получить значение текущего баланса приходится суммировать все транзакции по данному счету, начиная от сотворения мира. Нет, я не спорю, бывают и гораздо более сложные системы, но все-таки следование ряду хитрых принципов ДБ дизайна во многих случаях позволяет свести проблему масшабируемости по объему к простому наращиванию аппаратной мощИ. Так штааа... ![]() |
|||
|
||||
Chinook |
|
|||
Новичок Профиль Группа: Участник Сообщений: 8 Регистрация: 21.5.2005 Где: Vancouver, BC, Ca nada Репутация: нет Всего: нет |
В принципе, в правильно построенной OLAP базе все то же самое.
У нас как раз OLAP база, но уж очень сложная, как я уже говорил - тысячи таблиц/view. Ее бы тоже можно было бы причесать, но, во-первых, это делать пытаются, во-вторых, хоть админ у нас из физтеха(тоже русский) и весь с ног до головы в сертификатах, у него всего две руки и одна голова, в-третьих, очень сильно параметризированные отчеты, т.е. для оптимизации базы надо резко увеличить дублирование данных, а база и так уже занимает целую стойку компов. Да и приложение заточено под уже известные имена... Короче - начать и кончить. Единственная надежда - свалить отсюда побыстрее ![]() |
|||
|
||||
3,14 |
|
||||
![]() Эксперт ![]() ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 1614 Регистрация: 18.6.2004 Где: Н. Новгород Репутация: 3 Всего: 24 |
Это проблема легко решается средствами view, т.е. выносишь все сложные запросы в различные view, и собственно проблема решена, во всех виденных мной реализациях делалось именно так. Добавлено @ 08:46
Не совсем понял проблему обьясни по подробнее -------------------- Может быть, это только мой бред, Может быть, жизнь не так хороша, Может быть, я не выйду на свет, Но я летал, когда пела душа... |
||||
|
|||||
Chinook |
|
|||
Новичок Профиль Группа: Участник Сообщений: 8 Регистрация: 21.5.2005 Где: Vancouver, BC, Ca nada Репутация: нет Всего: нет |
На момент написания программы я не знаю, какие запросы будут обрабатываться. Т.е.: 1. Есть какой-то набор таблиц/view. 2. Пользователь выбирает на странице параметры (много параметров). На основе данных параметров надо сформировать запрос к БД. Т.е. в очень сильном приближении это выглядит так: 1. получем первый параметр п1, на его основе определяем, что используется таблица Т1 2. получаем второй параметр п2 - таблица Т2 3. третий п3 - Т3 4. четвертый п4 - надо сформировать select из таблицы Т4, Т5, Т6, Т7, параметрами запроса являются поля п5, п6, п7 5. получаем п7 - и т.д. При этом какие таблицы использовать, мы узнаем только в процессе работы, т.к. эти данные выбает некая структура таблиц в базе данных(metadata). Если надо добавить новые значения параметров и новое поведение программы, то все это добавляется в БД, а не в код. Но metadata говорит мне, что вот это, например, поля вывода (select), вот это таблица/view/подзапрос, а это where. Т.е. сейчас это последовательность операторов if...else and switch. Это не очень приятно: 1. select a1, a2, a3 from a where a5 = 1000 2. Теперь выясняется, что надо еще использовать таблицу b. Я должен найти слово "from " и вставить вместо него "from b, ", дальше найти слово "where " и заменить его на "where b.b1 = a.a1 and ". Результат: "select a1, a2, a3 from b, a where b.b1 = a.a1 and a5 = 1000" 3. после этого выясняется, что в запросе участвует таблица с... И опять сначала. А таких таблиц может быть, например, 30, половина из которых в подзапросах. Можно себе представить, как выглядит код. Именно формирование строки и нужно автоматизировать. Впрочем, никто, кроме меня, такой необходимости не видит. |
|||
|
||||
Stampede |
|
|||
![]() Гносеолог ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 963 Регистрация: 25.4.2005 Где: Calgary, Alberta, Canada Репутация: 66 Всего: 144 |
Да видим, видим. Тут важно понять вот какую вещь. У тебя есть юзер-интерфейсное представление запроса, а тебе надо преобразовать его в SQL эквивалент. Если пытаться это делать напрямую, то получится невообразимая каша - то, что имеет место в вашей системе. А если подойти к решению по-грамотному, то станет очевидно, что между этими двумя представлениями нужно ввести еще одно, промежуточное - программную модель запроса. После этого преобразование из одного в другое сведется к цепочке: юзерское представление - программное представление - код SQL Как это можно сделать, я уже писал. Если тебя пугает необходимость писания этого вручную, попробуй HQL или JDO (я, кстати, некоторые идеи для своего фреймворка подсмотрел именно там, но в чистом виде ни то ни другое использовать не мог по причине специфических структур хранения данных в моей системе). И эта... ты там вообще хоть пробуешь что-нибудь? А ты уже неделю жалуешься, а делать - не делаешь. Короче, держи нас в курсе - нам тоже интересно ![]() |
|||
|
||||
Chinook |
|
||||
Новичок Профиль Группа: Участник Сообщений: 8 Регистрация: 21.5.2005 Где: Vancouver, BC, Ca nada Репутация: нет Всего: нет |
Это понятно, только "программное представление" у нас делится на две части: 1. matadata, которые переводят запрос клиента (поля формы) в набор сущьностей БД, она уже разработана, но не написана. 2. конструктор, как я описал выше(причем их десятки для разных отчетов), который я хочу заменить на свой.
Чуток. Дело в том, что мы "исправляем последние баги в предыдущей версии, чтобы начать разработку принципиально новой". После 13 лет опыта работы я отчетливо понимаю, что это процесс бесконечный, тем более в таком спагетти как у наше, но манагер у нас с двумя годами опыта, молодой и горячий, делатель конфеток, блин, да еще с южным темпераментом (иранец), так что придется подождать, пока ему сверху все подробно и по морде объяснят. А вообще-то меня тут все достало и я ищу другую работу :-) |
||||
|
|||||
![]() ![]() ![]() |
Правила форума "Java" | |
|
Если Вам помогли, и атмосфера форума Вам понравилась, то заходите к нам чаще! С уважением, LSD, AntonSaburov, powerOn, tux. |
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей) | |
0 Пользователей: | |
« Предыдущая тема | Java EE (J2EE) и Spring | Следующая тема » |
|
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности Powered by Invision Power Board(R) 1.3 © 2003 IPS, Inc. |