DataValve — это бесплатная библиотека с открытым исходным кодом, которая облегчает создание повторно используемых компонентов представления и доступа к данным, а также предоставляет ряд функций для разбивки на страницы, сортировки и параметризации запросов. В этой статье определяются проблемы, которые DataValve стремится решить, и способы их решения.
Необходимость в DataValve
Большинству приложений, управляемых данными, необходимо отображать данные, организованные в страницы. Будь то результаты поиска или отчеты о продажах, выгрузка полного набора результатов данных на веб-страницу или в сетку Swing рано или поздно столкнется с проблемами по мере увеличения наборов данных.
Визуальные веб-фреймворки, такие как Wicket или JSF, обычно имеют некоторые компоненты, которые облегчают разбиение на страницы, а большинство фреймворков доступа к данным, таких как JDBC, Hibernate или JPA, имеют средства для доступа к результатам в виде страниц. Как правило, однако, вам нужно написать собственный код, чтобы преодолеть разрыв между представлением и механизмом доступа к данным. Если вы пишете свой собственный связующий код, вам, вероятно, придется переписывать его снова и снова, если вы хотите повторно использовать внутренний код с другим уровнем представления или изменить механизм доступа к данным.
Приложения с DataValve
Экзотические источники данных
Могут даже возникнуть ситуации, когда у вас возникнет внезапный запрос на предоставление доступа к страницам с разбивкой по страницам на более экзотические источники данных, такие как XML, CSV и двоичные файлы, даже при извлечении данных из веб-службы. В таком сценарии вы не только должны написать большую часть нумерации страниц самостоятельно, но опять же, вашему уровню представления потребуется некоторая работа для обработки нового механизма доступа к данным. Это также включает постраничный доступ к данным в памяти, которые регулярно используются, но редко изменяются и кэшируются в памяти.
Большинство из вас, вероятно, думают, что это не большая проблема, потому что вы пишете многоуровневый код с интерфейсом, который может иметь различные реализации. Вы правы, логическая организация кода может помочь со всеми этими проблемами, однако, в конце концов, вам все равно придется написать что-то, что интегрируется с новым источником данных так же, как старый источник данных в реализации.
Если вы думаете, что вам никогда не понадобится менять слой представления или поставщиков данных, все еще есть некоторые проблемы, с которыми вы можете подумать, что DataValve может помочь в этом.
Динамический запрос
Одна общая черта в приложениях, управляемых данными, состоит в том, чтобы запрашивать набор данных, используя параметры с возможностью исключать предложения запроса, где параметр не установлен. Если вы ищете людей, зачем фильтровать по адресу, если пользователь не вводил адрес? Решения, позволяющие обойти эту проблему, включают либо построение запроса во время выполнения и игнорирование ограничений с отсутствующими значениями параметров, либо включение потенциально нулевого параметра в самом запросе, что делает его уродливым и может снизить производительность.
Заказ данных безопасно
Другая проблема — абстракция порядка данных, особенно в веб-приложениях. Вы не хотите хранить фактические столбцы SQL, используемые для сортировки данных на веб-странице в случае атак с использованием SQL-инъекций. Наилучший способ — использовать ключ, отображающий порядок упорядоченных данных. Вы также должны были бы учесть возрастающий и нисходящий порядок.
Параметризация запросов
Динамические запросы должны быть параметризованы, чтобы обеспечить возможность безопасной установки переменных в запросе, а не изменения текста запроса, что может снова привести к атакам с использованием SQL-инъекций. Как правило, хотя большинство параметров нужно устанавливать вручную, и много раз мы добавляем новые параметры и забываем их устанавливать. Процесс добавления и назначения параметров может потребовать длинного стандартного кода. DataValve предлагает значительно улучшенную обработку параметров для запросов.
Как DataValve решает все эти проблемы
DataValve работает с использованием четко определенного интерфейса для доступа и разбивки на страницы данных, которые поступают от поставщика данных. Существуют разные поставщики данных для каждого типа источника данных (JDBC, ORM, Hibernate, File based), и вы даже можете написать свой собственный, используя интерфейс всего двумя способами. Поскольку у нас есть четко определенный интерфейс для данных, мы можем создать компоненты для представления, чтобы подключиться к этому интерфейсу. Поскольку мы связаны только с интерфейсом, мы можем изменить реализацию, и пока она возвращает объекты сущности того же типа, наш код представления остается неизменным. Представлению не важно, происходит ли список сущностей Person из базы данных, файла XML, веб-службы или списка в памяти. Когда набор данных подключен к другому слою представления, он связан с тем же интерфейсом и получает доступ к данным таким же определенным способом.
Вот краткий пример того, как мы создаем поставщика данных и разбиваем результаты на страницы.
DataProvider<Person> provider = new SomeDataProvider<Person>(); Paginator paginator = new DefaultPaginator(); paginator.setMaxRows(10); pagiantor.setFirstResult(15); List<Person> results = provider.fetchResults(paginator); for (Person p : results) { //do something with the 10 Person objects from 15-25 }
Мы также можем использовать набор данных, который представляет собой комбинацию Paginator и ссылки на поставщика данных.
DataProvider<Person> provider = new SomeDataProvider<Person>(); ObjectDataset<Person> dataset = new DataProviderDataset(provider); dataset.setMaxRows(10); dataset.setFirstResult(35); List<Person> results = dataset.getResults(); for (Person p : results) { ...some code... }
Каждый раз, когда мы вызываем getResults () для набора данных, он будет возвращать один и тот же список результатов, пока мы не перейдем на другую страницу результатов или вручную не аннулируем набор результатов. Каждый раз, когда вы вызываете fetchResults для поставщика данных, список результатов переизбирается каждый раз. Набор данных также реализует интерфейс итератора, чтобы вы могли выполнять итерацию по всему набору данных, и он проходил бы по каждой строке, но загружал их в пакетах по размеру пагинатора.
Запрошенные результаты
Давайте загрузим результирующий набор, который использует некоторую фильтрацию и сортировку.
QueryProvider<Person> provider = new JpaDataProvider<Person>(em); QueryDataset<Person> dataset = new QueryDataset<Person>(provider); provider.setSelectStatement("select p from Person p"); provider.setCountStatement("select count(p) from Person p"); //we can also access the provider using // dataset.getProvider() //add simple restriction provider.addRestriction("p.department.id = 4"); List<Person> results = dataset.getResultList(); for (Person p : results) { System.out.println(p.getName()); }
В качестве альтернативы, мы могли бы указать параметры, используя:
//:param is a magic value for this method call only provider.addRestriction("p.firstName = :param",someFirstName); //alternatively provider.addRestriction("p.firstName = :aFirstName"); provider.addParameter("aFirstName",someFirstName);
Несколько вещей, чтобы отметить здесь. Второй метод использования параметра требует двух вызовов метода, но вы можете изменить значение параметра позже перед выполнением, и если aFirstName имеет значение null, то ограничение не будет включено в выполненный окончательный запрос. Как правило, ограничения исключаются во время выполнения запроса, но если вы используете первый синтаксис, ограничение будет пропущено немедленно, если значение равно нулю, поскольку вы устанавливаете параметр как константу, а если значение равно нулю, то оно будет всегда быть нулевым.
Вот несколько альтернативных способов определения параметров, все из которых подчиняются правилу, согласно которому, если параметр имеет значение null, ограничение не добавляется. Все эти примеры могут использоваться с поставщиками данных Hibernate, JPA или JDBC, единственными изменениями являются спецификация запроса и имена полей в ограничениях.
//WRONG - if first name is null, then the param value is 'null%' and will be added provider.addRestriction("p.firstName like :param",criteria.firstName+"%"); //CORRECT - this checks one value, but assigns another to the actual parameter provider.addRestriction("p.firstName like :param",criteria.firstName,criteria.firstName+"%");
Это действительно полезно, когда мы хотим использовать одно значение для проверки нуля, но назначить другое значение фактическому параметру, чтобы мы могли использовать «начальный с» при поиске, добавляя подстановочный знак в конце.
provider.addRestriction("p.department.id = 4"); provider.addRestriction("or p.managerFlag = 1");
Все ограничения объединяются с «И», если они фактически не начинаются с логического оператора. Круглые скобки могут быть введены в запрос, но следует позаботиться о том, чтобы открывающие или закрывающие скобки не удалялись в случае нулевого параметра.
Если вы используете JSF или какую-либо другую среду с поддержкой EL, вы также можете использовать выражения EL непосредственно в запросах, если вы подключаете ELParameterResolver к поставщику.
provider.addRestriction("p.firstName = #{criteria.firstName}"); provider.addRestriction("p.lastName = #{criteria.lastName}");
Опять же, если эти выражения имеют нулевое значение, то ограничение не используется. Это позволяет очень легко создать бин критериев поиска, который подключается к JSF-интерфейсу и бэкэнду без какого-либо дополнительного кода клея.
Резолверы параметров
DataValve имеет концепцию преобразователей параметров, которые можно использовать в любом поставщике данных, который реализует интерфейс ParameterizedDataset. Средства определения параметров похожи на плагины, которые обеспечивают разрешение параметров с помощью кода. ReflectionParameterResolver использует отражение для поиска значений параметров, где имя свойства совпадает с именем параметра.
provider.addRestriction("p.firstName like :firstName"); provider.addRestriction("p.lastName like :lastName"); provider.addParameterResolver(new ReflectionParameterResolver(criteria));
Наш объект критериев имеет свойства имени и фамилии, которые используются для разрешения значений параметров в ограничениях. Опять же, если значения принимают нулевое значение, ограничение не используется при выполнении запроса.
Кроме того, к поставщику можно подключить несколько преобразователей параметров, чтобы вы могли использовать преобразователь параметров EL и преобразователь параметров отражения. Это может быть полезно для смешивания, если у вас есть несколько источников для параметров. Вы также можете комбинировать различные комбинации добавления ограничений.
provider.addRestriction("p.firstName like :firstName"); provider.addRestriction("p.lastName like :lastName"); provider.addRestriction("p.salesRep.id = #{currentUser.id}"); provider.addRestriction("p.dept.id = ",selectedDepartment.getId()); provider.addParameterResolver(new ReflectionParameterResolver(criteria)); provider.addParameterResolver(new ElParameterResolver());
Результаты заказа
Настройка заказа проста, мы добавляем значения ключа заказа и связываем информацию с каждым ключом, чтобы указать, как выполняется порядок, который в большинстве случаев представляет собой список полей, которые мы используем для заказа.
provider.getOrderKeyMap().put("name","p.lastName,p.firstName"); provider.getOrderKeyMap().put("age","p.dateOfBirth"); provider.getOrderKeyMap().put("id","p.id");
Чтобы упорядочить данные, когда мы получаем их от провайдера, нам нужно назначить ключевое значение порядка на paginator, используемом для извлечения данных от провайдера.
Paginator paginator = new DefaultPaginator(); paginator.setOrderKey("name"); //order by the persons name paginator.setOrderAscending(false); List<Person> orderedList = provider.fetchResults(paginator);
Если вы используете набор данных, вы можете просто установить свойства orderKey и orderAscending для набора данных. Эти ключевые значения могут быть встроены в вашу веб-страницу и переданы обратно на серверные компоненты, не беспокоясь о внедрении SQL. Если ключ заказа не распознается как одно из доступных значений в карте ключей заказа, то порядок не используется и SQL не вводится.
Государственное управление
Разбиватели страниц и поставщики данных являются отдельными экземплярами, в то время как наборы объектных данных состоят из разбивателя страниц со ссылкой на провайдера данных, которая хранит самый последний набор результатов. Причина этого в том, что вы можете изменить степень использованного безгражданства.
You can make the whole thing stateless with the paginator info being passed between the client and the server. With Wicket, Seam, CDI and Spring Web Flow you might want to keep the paginator state on the server side but keep refreshing the results each time you postback. This can be done by keeping it in a Conversation, a Spring web flow, or a Wicket page. If you want to go fully stateful, you can use the Dataset which includes the paginator and the data provider and caches the recent resultset and put it in a Wicket page, a conversation or a page flow. By default, the JSF Components for paginating a DataValve result set is stateless.
Client Side
Briefly, lets look at the options for creating clients for DataValve datasets. There are components for a simple JSF paginator and a sortable column header for JSF 2.0 in the datavalve-faces module, and a SortableDataProvider for Wicket. For Swing, there is a special TableModel that caches and pre-emptively loads data for display in a Swing JTable. This means you can easily navigate thousands of rows of data in a Swing Table without having a long initial load time or taking up a lot of memory for object storage.
In the DataValve download there are demos for each of these examples in projects that are easy to run using the Jetty plugin for Maven so no server is required. The Wicket demo even demonstrates how different pieces of server side code can be reused with JDBC, CSV and Hibernate driven data providers.
Get Started
In addition to the DataValve project page you can see the documentation in HTML, Single HTML, or PDF. The download includes source that can be built and installed with maven as well as pre-built binaries.
From http://www.andygibson.net/blog/article/introducing-datavalve/