Классика баз данных - статьи

       

Проблема механизма выборки данных


Пусть теперь интересующая нас сущность хранится в базе данных. Каким образом можно ее выбрать? В чистом объектно-ориентированном подходе для выборки следовало бы использовать объектный подход, в котором для идентификации объекта(ов) используется синтаксис в стиле конструктора, но, к сожалению, синтаксис конструкторов не является настолько общим, чтобы обеспечить необходимую гибкость. В частности, в нем отсутствует возможность инициализации коллекции объектов, а часто требуются именно запросы, возвращающие коллекции, а не одиночные сущности. (Несколько обращений к базе данных для выборки требуемых сущностей-членов коллекции обычно считаются слишком расточительными по отношению и ко времени задержки, и к пропускной способности, чтобы этот вариант можно было полагать разумной альтернативой. Подробности см. в разд. «Парадокс загрузки».) В результате обычно применяются подходы Query-By-Example (QBE), Query-By-API (QBA) или Query-By-Language (QBL)

Подход QBE характеризуется тем, что заполняется шаблон объекта того объектного типа, к которому направлен поиск. В поля шаблона объекта заносятся значения, используемые для фильтрации значений в процессе выполнения запроса. Например, если задается запрос к таблице объектов Person для выборки людей по фамилии Smith, то этот запрос при применении подхода QBE будет выглядеть примерно так:

Person p = new Person(); // assumes all fields are set to null by default

p.LastName = "Smith";

ObjectCollection oc = QueryExecutor.execute(p);

Проблема подхода QBE очевидна: он прекрасно подходит для простых запросов, но не является достаточно выразительным для поддержки более сложного стиля запросов, который часто требуется – «найти всех людей по фамилии Smith или Cromwell», «найти всех людей, фамилией которых не является Smith» и т.д. Хотя в принципе можно расширить подход QBE так, чтобы он справлялся с такими (и более сложными) запросами, это, безусловно, приведет к существенному усложнению API.
Более важно то, что применение подхода QBE создает неудобства для прикладных объектов – в них должны будут поддерживаться поля/свойства, допускающие наличие неопределенных значений, что может быть нарушением правил прикладной области, поддержку которых стремится обеспечить объектный мир. Во многих сценариях использования человек без имени не является слишком полезным объектом, а именно этого потребует подход QBE от прикладных объектов, хранимых в соответствующей базе данных. (Профессионалы QBE могут возразить, что в реализации объектов неразумно принимать это во внимание, но это, опять же, нелегко и часто не делается.)

В результате второй шаг обычно состоит в поддержке объектной системой подхода «Query-By-API», в котором запросы конструируются путем использования объектов-запросов, обычно примерно в такой форме:

Query q = new Query();

q.From("PERSON").Where(

new EqualsCriteria("PERSON.LAST_NAME", "Smith"));

ObjectCollection oc = QueryExecutor.execute(q);



Здесь запрос основывается не на пустом «шаблоне» выбираемого объекта, а на наборе «объектов-запросов», которые совместно используются для определения объекта в стиле команды, предназначенной для выполнения над базой данных. Несколько критериев комбинируется путем использования некоторой конструкции, обычно соединяющей через «And» и «Or» объекты, каждый из которых содержит уникальный объект-критерий, задающий часть условия выборки. К концу запроса могут быть добавлены вызовы объектов фильтрации/манипулирования, такие как «OrderBy(field-name)» или «GroupBy(field-name)». В некоторых случая эти вызовы методов в действительности ведут в объекты, конструируемые программистом и явно связываемые между собой.

Разработчики часто отмечают, что подход QBA является намного более многословным, чем традиционный подход SQL, и при использовании этого подхода намного более трудно (если не невозможно) представить некоторые виды запросов (в частности, внешние соединения).



Кроме этого, имеется более тонкая проблема: в надежде на дисциплину разработчиков как имя таблицы (PERSON), так и имя столбца в критерии (PERSON.LAST_NAME) представляются в виде стандартных строк символов, принимаемых в том виде, как они задаются разработчиком. Это приводит к тому, что до времени выполнения объектной системы не может быть произведена какая-либо проверка правильности запросов. Это является классической проблемой программирования, ошибкой «толстых пальцев», которая выражается в том, что запрос направляется не к желаемой таблице PERSON, а к какой-то другой таблице PRESON. Хотя эта ошибка будет быстро обнаружена при тестировании соответствующего компонента совместно с реальным экземпляром базы данных, это предполагает наличие двух обстоятельств – то, что разработчики добросовестно относятся к тестированию компонентов, и то, что тестирование компонентов производится с использованием реальной базы данных. Хотя первая ситуация становится более или менее гарантированной по мере того, как все больше разработчиков становится «тест-инфицированными» (следуя терминологии Гаммы и Бека, Erich Gamma, Kent Beck), возможность наличия второго обстоятельства все еще является предметом обсуждений, поскольку налаживание и разборка экземпляра базы данных, пригодного для тестирования компонентов, остается трудным делом. (Хотя имеется несколько способов преодоления этой проблемы, как кажется, на практике они используются незначительно.)

Также имеется та проблема, что от разработчиков требуется большее понимание логического (или физического) представления данных. Вместо того чтобы просто сосредоточиться на том, как объекты связаны между собой (через простые ассоциации, такие как массивы или коллекции экземпляров), разработчику теперь требуется больше знать о том, как объекты хранятся, что делает систему более уязвимой к изменениям схемы базы данных. Эту проблему иногда можно обойти за счет применения гибридного подхода, при котором система берет на себя ответственность за интерпретацию ассоциаций, и разработчик может писать примерно следующее:



Query q = new Query();

Field lastNameFieldFromPerson =
Person.class.getDeclaredField("lastName");

q.From(Person.class).Where(new

EqualsCriteria(lastNameFieldFromPerson, "Smith"));

ObjectCollection oc = QueryExecutor.execute(q);

Здесь частично решаются проблемы потребности знания схемы и «толстых пальцев», но не исчезает многословность, и по-прежнему остается трудным составление более сложных запросов, таких как запрос над несколькими таблицами (или, если угодно, классами), соединяемыми разными способами по нескольким критериям.

Поэтому следующая задача состоит в построении подхода «Query-By-Language», в котором создается новый язык, похожий на SQL, но в чем-то его «превосходящий», для поддержки сложных и мощных запросов, обычно поддерживаемых SQL. Примерами такого языка являются OQL и HQL. Здесь проблемой является то, что эти языки часто являются подмножествами SQL, и поэтому они не обладают полной мощностью SQL. Более важно то, что в слое ОР-отображения теперь теряется важный «выигрышный момент» – мантра «объекты и только объекты», которая породила этот подход. Использование SQL-подобного языка – это почти использование самого SQL, и как такой язык может быть более «объектным»? Хотя разработчикам может не потребоваться осведомленность о физической схеме модели данных (интерпретатор/исполнитель языка запросов может произвести упомянутое выше отображение), разработчикам понадобится знание того, как в языке представляются ассоциации и свойства, и подмножество объектных возможностей внутри языка запросов. Например, будет ли возможным написать следующее?

SELECT Person p1, Person p2

FROM Person

WHERE p1.getSpouse() == null

AND p2.getSpouse() == null

AND p1.isThisAnAcceptableSpouse(p2)

AND p2.isThisAnAcceptableSpouse(p1);

Другими словами, здесь требуется просканировать базу данных и найти все пары людей, являющихся приемлемыми по отношению друг к другу.


Хотя метод «isThisAnAcceptableSpouse», очевидно, относится к классу Person ( у каждого экземпляра этого класса может иметься собственный критерий, по которому оценивается приемлемость другого экземпляра – он может быть блондином, брюнетом, рыжим, зарабатывать больше 100000 долларов в год и т.д.), непонятно, можно ли выполнять этот метод в языке запросов, и должна ли существовать такая возможность. Даже в наиболее тривиальных реализациях производительности может быть нанесен серьезный урон, в особенности, если слой ОР-отображения должен для выполнения запроса преобразовывать данные реляционных столбцов в объекты. Кроме того, отсутствует гарантия того, что разработчик напишет этот метод полностью эффективно, и нет никакого способа заставить его выполнить эффективную реализацию.

(Критики могут возразить, что это решаемая проблема, и предложить два возможных решения. Одно из них состоит в том, чтобы поместить данные о предпочтениях в отдельную таблицу и включить эту таблицу в запрос. Однако это приведет к ужасно сложному запросу, текст которого займет несколько страниц, и при потребности добавления нового критерия предпочтения для распутывания этого запроса придется привлекать эксперта по SQL. Другим решения является размещение реализации «приемлемости» в хранимой процедуре базы данных. Тогда соответствующий код будет полностью удален из объектной модели, что оставит разработчиков вообще без объектного решения. Это решение допустимо, но только если принять допущение, что в объектной модели может присутствовать не вся реализация. Но это противоречит исходному условию «объекты и ничего, кроме объектов», на котором многие защитники ОР-отображения основывают свои доводы.)


Содержание раздела