Создание веб-приложений с использованием современных ORM-фреймворков

Объектно-реляционное отображение. ORM-фреймворки. Загрузка по требованию как шаблон проектирования. Способы расширения классов-сущностей. Внедрение в байт-код. Загрузка полей и свойств сущностей в detached состоянии. Механизм пакетной выборки.

Рубрика Программирование, компьютеры и кибернетика
Вид курсовая работа
Язык русский
Дата добавления 07.07.2012
Размер файла 1,1 M

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

СОДЕРЖАНИЕ

  • СОДЕРЖАНИЕ
  • ВВЕДЕНИЕ
  • 1. ОПИСАНИЕ ПРЕДМЕТНОЙ ОБЛАСТИ
  • 1.1 Объектно-реляционное отображение. ORM-фреймворки.
  • 1.1.1 Hibernate
  • 1.1.2 EclipseLink
  • 1.1.3 OpenJPA
  • 1.1.4 Java Persistence API
  • 1.2 Загрузка по требованию, как шаблон проектирования
  • 1.3 Java EE и Spring
  • 2. ОСОБЕННОСТИ КОНФИГУРАЦИИ JPA ПРОВАЙДЕРОВ
  • 2.1 Способы расширения классов-сущностей
  • 2.1.1 Прокси-классы
  • 2.1.2 Внедрение в байт-код
  • 2.2 Загрузка по требованию полей и свойств сущностей в detached состоянии.
  • 3. СПОСОБЫ ОПТИМИЗАЦИИ ЗАГРУЗКИ ПО ТРЕБОВАНИЮ
  • 3.1 JPA
  • 3.2 Hibernate, EclipseLink и OpenJPA
  • 3.2.1 Extra-lazy коллекции
  • 3.2.2 Fetch groups
  • 3.2.3 Пакетная выборка
  • 4. ПРОБЛЕМЫ ЗАГРУЗКИ ПО ТРЕБОВАНИЮ
  • 4.1 Проблема N+1 запросов
  • 5. ОЦЕНКА ЭФФЕКТИВНОСТИ РАЗЛИЧНЫХ СПОСОБОВ ОПТИМИЗАЦИИ ЗАГРУЗКИ ПО ТРЕБОВАНИЮ
  • 5.1 Демонстрационное приложение “Тестер”
  • 5.1.1 Функциональные требования
  • 5.1.2 Архитектура приложения
  • 5.1.3 Доменная модель приложения
  • 5.1.4 Страница преподавателя
  • 5.2 Способы оптимизации
  • 5.2.1 Предварительная загрузка
  • 5.2.2 Пакетная выборка
  • 5.2.3 JPQL запросы с использованием JOIN FETCH
  • 5.3 Оцениваемые параметры
  • 5.4 Условия сравнения
  • 5.5 Результаты сравнения
  • ЗАКЛЮЧЕНИЕ
  • СПИСОК ЛИТЕРАТУРЫ

ВВЕДЕНИЕ

На сегодняшний день в подавляющем большинстве случаев для хранения данных используются реляционные базы данных. В приложениях на языке Java для взаимодействия с базами данных, используется интерфейс JDBC, который предоставляет минимальный функционал для исполнения запросов к базе данных на языке SQL и обработки их результатов. JDBC может оперировать с БД в терминах строк, таблиц и отношений, однако в таком виде с данными на уровне приложения работать чаще всего неудобно. Проще, когда эти данные представлены в виде объектов и связей между ними. Задача отображения структуры объектов модели предметной области (с которой программист работает на уровне приложения), на реляционную модель данных (с которой работают реляционные базы данных), решается в рамках объектно-реляционного отображения (object-relational mapping, ORM). Логика этого отображения может быть реализована с использованием различных фреймворков объектно-реляционного отображения (ORM-фреймворков), при этом создается эффект “виртуальной объектной базы данных”, взаимодействие с которой, осуществляется не в терминах таблиц, строк и отношений, а в терминах объектов и связей между ними. Использование ORM-фреймворков ограждает разработчика от написания большого количества однообразного и подверженного ошибкам кода, а также от особенностей конкретной реляционной базы данных, тем самым, во многом упрощая разработку более гибких и проще расширяемых приложений. Но даже с использованием ORM-фреймворков разработчик не может полностью абстрагироваться от некоторых особенностей реализации объектно-реляционного отображения.

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

С одной стороны, все поля объекта могут быть загружены сразу при загрузке объекта из БД, что может быть удобно, если значения полей находятся, например, в той же таблице, и это обычная практика, если эти поля содержат числовые, строковые значения или даты. Но поля объекта также могут содержать сложные типы, ссылаясь на другие объекты, которые отображаются на другие таблицы. Если связями охвачено много объектов, для их загрузки может потребоваться сделать несколько запросов к БД, причем большая часть данные может впоследствии так и не быть использована.

Чтобы исключить подобное неэффективное поведение системы используется подход, при котором момент загрузки определенных полей объекта откладывается до той поры, пока их значение не будет необходимо. Этот подход носит название “lazy loading” (“отложенная загрузка”, “ленивая загрузка” или “загрузка по требованию”) и позволяет минимизировать количество обращений к базе данных.

При использовании готового ORM-фреймворка, использование механизма загрузки по требованию сопряжено, в определенных случаях, со специфическими для данной области проблемами, которые нельзя решить в рамках объектной модели приложения. Эти проблемы, а также различные особенности использования загрузки по требованию, которые должны учитываться при создании веб-приложений с использованием современных ORM-фреймворков и будут исследованы в данной работе.

1. ОПИСАНИЕ ПРЕДМЕТНОЙ ОБЛАСТИ

1.1 Объектно-реляционное отображение. ORM-фреймворки

Подход, при котором логика хранения данных отделена от логики непосредственного использования этих данных, распространен уже давно. Такое разделение позволяет разработчикам в каждой области сосредоточиться на предмете своей специальности, помогая разрабатывать и поддерживать масштабные системы проще и быстрее. С другой стороны, более сложным становится аспект взаимодействия этих двух компонент.

В подавляющем большинстве случаев, сегодня, для хранения данных, используются реляционные базы данных. Для взаимодействия Java-приложений с БД используется интерфейс JDBC, реализуемый разработчиками соответствующих баз данных. Через этот интерфейс, разработчик конечного приложения может осуществлять SQL-запросы к базе данных и обрабатывать их результаты, то есть оперировать с данными в том виде, в котором они представлены в базе данных, в терминах таблиц, строк и отношений (relations). Java же, как язык объектно-ориентированный, предполагает работу в рамках модели предметной области (domain model) в терминах объектов и связей между ними. Механизм объектно-реляционного отображения (object-relational mapping, ORM), связывает базы данных с концепциями объектно-ориентированных языков программирования таким образом, что создается эффект “объектной базы данных”, с которой можно взаимодействовать на уровне приложения в терминах объектов и связей.

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

Далее представлены три самых широко используемых на сегодняшний день Java ORM-фреймворка. Вместе они предоставляют во многом схожий функционал, но каждый реализует его по-своему. Каждый из них сейчас основан на проекте с открытым исходным кодом (open source).

1.1.1 Hibernate

Из всей тройки наиболее широко распространен и имеет самое большое сообщество разработчиков. Разрабатывается JBoss community под эгидой Red Hat. Поставляется вместе с JBoss Application Server.

1.1.2 EclipseLink

Разрабатывается сообществом Eclipse Foundation. Основан на исходных кодах проекта TopLink 11g, переданных в открытое пользование компанией Oracle. Является эталонной реализацией (reference implementation) JPA 2.0, и поставляется вместе с сервером приложений GlassFish.

1.1.3 OpenJPA

Под этим именем (OpenJPA) часть кода была пожертвована Apache Software Foundation проектом Kodo, который помимо JPA также реализовывал спецификацию JDO.

1.1.4 Java Persistence API

Каждый из вышеописанных фреймворков реализует спецификацию Java Persistence API -- стандарт взаимодействия с базами данных в приложениях на Java SE и Java EE.

JPA как технология, обеспечивает объектно-реляционное отображение простых Java объектов (POJO) и предоставляет API для сохранения, получения и управления такими объектами. Первая версия JPA разрабатывалась как часть спецификации EJB3 и увидела свет с выходом Java EE 5 в 2006 году.

Важно подчеркнуть, что JPA это именно спецификация, которая впоследствии реализуется различными JPA провайдерами, среди которых три описанных ранее решения.

Спецификация JPA в себя включает:

? Набор программных интерфейсов (API), позволяющий организовать взаимодействие с JPA провайдером.

? Платформенно-независимый объектно-ориентированный язык запросов Java Persistence Query Language (JPQL).

? Метаданные, с помощью которых определяются основные правила отображения объектов Java на таблицы реляционной БД. Для описания этих правил используются либо XML файлы, либо аннотаций Java 5.

Многие значительные моменты реализации не находят отображения в рамках спецификации JPA, их трактовка, таким образом, полностью ложится на плечи конкретного JPA провайдера. В особых случаях их поведение различных JPA провайдеров может различаться принципиально. Некоторые из таких моментов, имеющие непосредственное отношение рассматриваются далее.

Помимо этого каждый из JPA провайдеров (даже EclipseLink, который сейчас носит статус Reference Implementation для JPA 2.0) предоставляет расширенный функционал для работы со специфическими особенностями различных схем баз данных.

1.2 Загрузка по требованию, как шаблон проектирования

Объекты и реляционные базы данных используют разные механизмы структурирования данных. В реляционных базах данных не отображаются многие характеристики объектов, в частности коллекции и наследование. При построении объектной модели с большим объемом бизнес-логики и связей эти механизмы позволяют проще организовать данные внутри приложения и соответствующее им поведение. С другой стороны, использование подобных механизмов приводит к несовпадению объектной и реляционной схем.

Объектная модель приложения и реляционная модель базы данных должны обмениваться данными и несовпадение схем, делает эту задачу нетривиальной. Не всегда загрузка одного объекта предполагает выполнению одного SQL-запроса. Более того, как правило, объекты модели тесно связаны между собой, поэтому загрузку данных в память следует организовать таким образом, чтобы при загрузке интересующего вас объекта из базы данных автоматически извлекались и другие связанные с ним объекты. Это значительно облегчает жизнь разработчика, которому в противном случае необходимо организовывать загрузку всех связанных объектов вручную. [1. c.220]

Если связями охвачено много объектов, считывание любого из них приводит к необходимости загружать из базы данных целый граф объектов. Чтобы исключить подобное неэффективное поведение системы, необходимо ее ограничить, сократив количество загружаемых объектов, но оставить за собой право завершения операции, если потребность в дополнительной информации действительно возникнет.

Для решения этой проблемы и одновременной минимизации влияния на объекты, расположенные в памяти, слой отображения использует шаблон lazy loading ("ленивая загрузка" или "загрузка по требованию"), согласно которому, процесс загрузки прерывается на определенном моменте, оставляя соответствующую метку в структуре объектов. Это позволяет загрузить необходимые данные только тогда, когда они действительно понадобятся. [1. c.220]

Согласно [1] существует четыре основных способа реализации механизма загрузки по требованию:

? Lazy Initialization ("ленивая инициализация" или "инициализация по требованию")

? Virtual Proxy ("виртуальный прокси-объект")

? Value Holder ("контейнер значения" или "диспетчер значения")

? Ghost ("призрак" или "фиктивный объект")

Рисунок 1. Загрузка по требованию.

Самый простой из них -- инициализация по требованию (Lazy Initialization). Основная идея данного подхода заключается в том, что при попытке доступа к полю выполняется проверка, не содержит ли оно значение null. Если это так, соответствующий метод доступа (get-метод) загружает значение поля и лишь затем, возвращает его значение. Данный подход, однако, может быть использован только в том случае, если поле является самоинкапсулированным, то есть если доступ к такому полю осуществляется только посредством get-метода даже в рамках самого класса.

Использовать значение null в качестве признака незагруженного поля очень удобно. Исключение составляют лишь те ситуации, когда это значение является допустимым значением. В этом случае необходимо выбрать какой-нибудь другой признак того, что поле не загружено.

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

Описанных проблем можно избежать, если использовать шаблон, который носит название “контейнер значения” или "диспетчер значения" (Value Holder). Контейнер значения -- это объект, который играет роль оболочки для какого-нибудь другого объекта. Чтобы получить значение базового объекта, необходимо обратиться за ним к контейнеру значения. При первом обращении контейнер извлекает всю необходимую информацию из базы данных. Этот способ имеет ряд недостатков. Во-первых, класс должен знать о наличии контейнера значения, а во-вторых, теряются преимущества строгой типизации.

И наконец, призрак или фиктивный объект (Ghost) -- это реальный объект с неполным состоянием. Когда подобный объект загружается из базы данных, он содержит только свой идентификатор. При первой же попытке доступа к одному из его полей объект загружает значения всех остальных полей. Для большей наглядности фиктивный объект можно представить себе в виде объекта, все поля которого одновременно инициализируются по требованию, или объекта, который является собственным виртуальным прокси-объектом.

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

Виртуальный прокси-объект или фиктивный объект не обязательно должны быть лишены содержимого. Если реальный объект включает в себя быстро загружаемые или часто используемые данные, их можно загрузить вместе с прокси-объектом или фиктивным объектом (подобные объекты иногда называют "облегченными").

Многие проблемы реализации загрузки по требованию связаны с наследованием. Чтобы создавать фиктивные объекты, нужно знать, какого они должны быть типа. Однако зачастую это можно узнать только тогда, когда объект уже будет загружен. В статически типизированных языках подобными проблемами чревато и использование виртуальных прокси-объектов.

1.3 Java EE и Spring

Веб-приложения на Java для своей работы требуют наличия определенного окружения. Это может быть либо контейнер сервлетов (Tomcat, Jetty и др.), либо сервер приложений (Glassfish, JBoss AS и др.). Выбор того или другого из вариантов продиктован, во многом, масштабом приложения и необходимостью использования определенных сервисов. Контейнер сервлетов предоставляет лишь базовый набор сервисов и библиотек, которые доступны внутри приложения, и отлично подойдет для малых приложений, не создающих большого трафика. Корпоративные системы большого масштаба предъявляют к системе более серьезные требования. Например, такие системы часто используют для своей работы несколько физических серверов. Работая в рамках сервера приложений, разработчик получает такие сервисы как кластеризация, отказоустойчивость и балансировка нагрузки прямо “из коробки”, и может сфокусироваться только на реализации бизнес-логики приложения.

Java Platform, Enterprise Edition (Java EE), представляет собой набор спецификаций, описывающий архитектуру серверной платформы, для языка Java, благодаря которой обеспечивается простая переносимость приложений с одной платформы на другую. Java EE, расширяет возможности Java Platform, Standard Edition (Java SE), и включает в себя стандарты следующих технологий:

? Servlets

? Java Server Pages (JSP)

? JavaServer Faces (JSF)

? Enterprise JavaBeans (EJB)

? Java Persistence API (JPA)

? Java Transaction API (JTA)

? Java Bean Validation

? Contexts and Dependency Injection (CDI)

и многие другие.

Эталонной реализацией Java EE является сервер приложений Glassfish. Кроме того, полную поддержку Java EE на сегодняшний день имеют несколько серверов от разных производителей, например:

? IBM WebSphere

? JBoss Application Server

? Apache Geronimo

Spring Framework - это легковесный (lightweight) универсальный фреймворк, для платформы Java, разрабатываемый сейчас под эгидой SpringSource. В основе Spring лежит принцип инверсии управления (Inversion of Control, IOC), реализуемый за счет механизма внедрения зависимостей (Dependency Injection, DI), поэтому Spring Framework позиционируется как IOC-контейнер. Со Spring может быть интегрировано множество различных технологий, многие из которых коррелируют с технологиями Java EE (например, JPA и Bean Validation), поэтому Spring Framework представляется как альтернатива стандартному Java EE окружению. Приложения на Spring могут работать как в контейнере сервлетов, так и на сервере приложений, более того Spring может работать даже в контексте обычных, настольных (desktop) приложений.

Важно отметить, что для построения слоя сохранения в приложениях как на платформах Java EE, так и Spring, может использоваться технология Java Persistence API.

2. ОСОБЕННОСТИ КОНФИГУРАЦИИ JPA ПРОВАЙДЕРОВ

В процессе конфигурации различных JPA провайдеров для работы в контексте Spring и Java EE, всегда нужно иметь в виду несколько моментов касательно того как реализуется загрузка по требованию на самом нижнем уровне и когда она может быть использована.

2.1 Способы расширения классов-сущностей

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

Спецификация JPA не предписывает, каким образом этот механизм слежения должен осуществляться, поэтому существует несколько подходов для его реализации. Более того, каждый из трех JPA провайдера предоставляет несколько вариантов реализации этого механизма на выбор. Любой из вариантов, однако, так или иначе, предполагает, что исходные классы сущностей тем или иным образом расширяются на определенном этапе жизни приложения.

Таблица 1

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

Hibernate

EclipseLink

OpenJPA

proxy / subclass

Javassist / CGLIB / ASM / ...

byte code instrumentation weaving

class enhancement

build-time

Плагины для ANT / Maven / ...

deploy-time

-

Java EE 5 / Spring

run-time

-

javaagent

2.1.1 Прокси-классы

В простейшем случае расширение классов сущностей происходит за счет наследования. При помощи библиотеки Javassist, CGLIB или ASM для каждого класса “на лету” создается класс-наследник или прокси-класс, в котором переопределяются set- и get- методы класса, а также добавляются новые поля, необходимые для работы JPA провайдера. Такой подход может использоваться в любом окружении, где работают соответствующие библиотеки. По умолчанию, он используется в Hibernate, однако может быть доступен при использовании EclipseLink или OpenJPA. Для этого необходимо задать в persistence.xml следующие свойства:

<property name="eclipselink.weaving" value="false"/>

<property name="openjpa.RuntimeUnenhancedClasses" value="unsupported" />.

Не смотря на то, что данный подход сегодня используется очень широко, он скрывает множество потенциальных угроз. Поэтому, в случае OpenJPA, например, его советуют не использовать, о чем неоднократно упоминается на официальном сайте и в документации OpenJPA [5]. Причин для этого несколько.

Во-первых, при наследовании классов-сущностей, можно отследить и перехватить только обращения к свойствам, но не полям класса-сущности. Более того, в зависимости от конкретного провайдера поля исходного класса могут не использоваться вообще, оставаясь постоянно в значении null, в то время как обращение к get- и set- методам будет перенаправляться другому классу, для которого класс-наследник будет являться оберткой.[8] Поэтому, при использовании данного подхода важно, чтобы доступ к полям класса осуществлялся только через get- и set- методы, даже в рамках самого класса.

Во-вторых, экземпляры исходных классов и прокси-классов нельзя сравнивать на основе getClass(). Об этом часто забывают при реализации метода equals().

В-третьих, в тех случаях, когда имеет место иерархия из нескольких классов при сравнении классов-сущностей нельзя полагаться даже на результат instanceof. На рисунке 2 представлена иерархия из двух классов и то, как в нее вписывается прокси-класс.

Рисунок 2. Прокси-классы и наследование.

Прокси класс всегда создается в соответствии с некоторым заданным жестко типом, в данном случае это А, поэтому если в действительности это тип наследника A, например, B, об этом невозможно узнать на основе сравнения прокси-класса с помощью instanceof. Выходом из данной ситуации в рамках JPA может быть только отказ от прокси-классов. [8]

2.1.2 Внедрение в байт-код

Более прозрачный подход предполагает расширение исходных классов-сущностей, за счет внедрения в их байт-код, на определенном этапе жизненного цикла приложения. В случае каждого конкретного JPA провайдера, как ни странно, этот подход в документации именуется по разному, в случае Hibernate - это “bytecode instrumentation”, EclipseLink - “weaving”, а OpenJPA - “class enhancement”.

Непосредственно внедрение в байт-код может происходить на этапе: сборки приложения, загрузке его на сервер или в тот момент, когда классы-сущности загружаются в JVM.

Внедрение в байт-код на этапе сборки приложения (build-time).

В этом случае внедрение в байт-код классов-сущностей осуществляется еще до того как они упакованы в JAR/WAR/EAR файлы. Для этих целей служат специализированные средства, предоставляемые каждым из трех JPA провайдеров. Для автоматизации этого процесса, существует множество плагинов для сборщиков типа ant и maven.

Этот подход может использоваться при работе JPA провайдера в любом окружении и доступен даже для старых версий языка Java.

Внедрение в байт-код на этапе загрузки приложения на веб-сервер (deploy-time).

Такое поведение возможно только в контексте Java EE 5 совместимого сервера приложений или приложения на Spring с использованием <context:load-time-weaver/> (в том случае, если приложение загружается в сервлет-контейнер, необходим еще и -javaagent:spring-agent.jar). При этом классы, загружаемые на сервер, расширяются автоматически средствами Java 5 class retransformation. Такое поведение поддерживается только EclipseLink и OpenJPA и не требует дополнительной конфигурации.

Внедрение в байт-код на этапе загрузки классов в JVM (run-time).

Может осуществляться EclipseLink и OpenJPA посредством специального javaagent. Не требует дополнительной конфигурации и может использоваться в любом окружении, вплоть до настольных(desktop) приложений.

Подводя итоги, можно сказать, что EclipseLink и OpenJPA обладают более широким спектром возможностей в плане расширения классов-сущностей.

2.2 Загрузка по требованию полей и свойств сущностей в detached состоянии

JPA определяет жизненный цикл сущности, который состоит из четырех состояний: new/transient, managed, removed и detached. Сущность единовременно может находиться только в одном из них.

Рис. 3. Жизненный цикл сущности

Множество сущностей, в состоянии managed в каждый отдельный момент времени образуют т.н. контекст сохранения (Persistence Context). Обращение к полям и свойствам сущности, загружаемым по требованию, в рамках JPA разрешено только пока сущность находится в контексте сохранения. Контекст сохранения действует в рамках одной транзакции базы данных, после чего, все сущности, которые находились в нем, переходят в состояние detached.Однако в этом состоянии у сущностей могут оставаться незагруженные поля, к которым, необходимо осуществить доступ.

Как должен вести себя JPA провайдер в такой ситуации не предписано спецификацией, поэтому каждый конкретный JPA-провайдер ведет себя по-разному. Так, в случае Hibernate попытка доступа к незагруженным полям сущности завершиться выбросом исключительной ситуации org.hibernate.LazyInitializationException. При использовании OpenJPA в результатом такой операции будет значение null. EclipseLink в тех же условиях вернет необходимый результат.

Естественно, что поведение присущее Hibernate и OpenJPA часто неприемлемо. У данной проблемы есть несколько различных решений:

? Можно отказаться от загрузки по требованию.

Иногда, пересмотрев модель предметной области, можно отказаться от загрузки по требованию в том случае, где она вызывает проблемы подобного рода. Подробнее о тонкостях конфигурации загрузки по требованию на уровне доменной модели приложения говориться в следующей части.

? Можно вручную инициализировать все необходимые поля до завершения транзакции. Эта задача сводится к последовательному обращению ко всем полям сущности, которые будут необходимы после завершения транзакции. Для коллекций, загружаемых по требованию, обычно в таком случае вызывается метод size() или iterator().

В качестве примера рассмотрим модель студенческой группы, которая имеет название, список студентов и ссылается на преподавателя, который ее добавил. Если предположить что все поля группы загружаются по требованию, чтобы все они были доступны за рамками транзакции, внутри ее необходимо выполнить следующий код:

...

group.getName();

group.getAddedBy();

group.getStudents().size();

...

После чего все поля группы будут инициализированы.

Несмотря на то, что данный подход нивелирует все достоинства механизма загрузки по требованию, он может успешно применяться в том случае, например, когда между слоем представления и бизнес-логики имеет место промежуточный слой Data Transfer Object (DTO) (см. [1]).

? Чтобы сохранить сущность в состоянии managed, может быть использован шаблон OpenSessionInView / OpenEntityManagerInView, суть которого сводится к тому, что транзакция остается открытой на протяжении всего времени обработки запроса. Для этого может использоваться класс-фильтр (реализующий интерфейс javax.servlet.Filter), который открывает транзакцию в начале обработки запроса и закрывает по окончании. В рамках Spring Framework для этих целей есть набор готовых классов-фильтров: OpenSessionInViewFilter конкретно для Hibernate или OpenEntityManagerInViewFilter для JPA. Их достаточно зарегистрировать их в web.xml:

<filter>

<filter-name>

openEntityManagerInViewFilter

</filter-name>

<filter-class>

org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter

</filter-class>

</filter>

<filter-mapping>

<filter-name>

openEntityManagerInViewFilter

</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

Кроме этого Spring предоставляет альтернативный подход с использованием OpenSessionInViewInterceptor и OpenEntityManagerInViewInterceptor, использование которых можно настроить в контексте Spring и не нужно учитывать в web.xml.

В случае же Java EE классы этих фильтров необходимо реализовывать самостоятельно.

? Сущности могут сохранять состояние managed на протяжении даже нескольких запросов, когда используется расширенной контекст сохранения (Extended Persistence Context). В этом случае контекст сохранения не привязан к единой транзакции, и может сохраняться на протяжении долгого времени. Этот подход может стать причиной потребления большого объема памяти, потому в данном контексте его использовать нельзя.

Как итог, оптимальным решением для Hibernate и OpenJPA, является применение шаблона проектирования OpenSessionInView / OpenEntityManagerInView, для чего в рамках Spring Framework предоставляются готовые классы фильтров и интерцепторов. EclipseLink, в свою очередь не требует наличия никаких дополнительных средств и предоставляет функционал, необходимый для нормальной работы загрузки по требованию у сущностей в detached состоянии “из коробки”.

После того, как все три рассматриваемых JPA сконфигурированы для работы на платформах Java EE и Spring, можно перейти к рассмотрению способов оптимизации загрузки по требованию в рамках непосредственно предметной модели приложения.

3. СПОСОБЫ ОПТИМИЗАЦИИ ЗАГРУЗКИ ПО ТРЕБОВАНИЮ

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

3.1 JPA

В рамках JPA требуемая стратегия загрузки, может быть задана при помощи атрибута fetch, каждой из представленных аннотаций:

? @Basic

? @OneToMany

? @ManyToMany

? @ManyToOne

? @OneToOne

? @ElementCollection

Все поля, отмеченные fetch=FetchType.LAZY будут загружаться только по требованию, а отмеченные fetch=FetchType.EAGER - предварителльно.

По умолчанию значение fetch=FetchType.LAZY имеют аннотации @ManyToMany, @ManyToOne и @ElementCollection, и используется в рамках интерфейсов следующих коллекций:

? java.util.Collection

? java.util.Set

? java.util.List

? java.util.Map

Каждый конкретный JPA провайдер реализует по-своему интерфейсы этих коллекций, для обеспечения механизма загрузки по требованию. По умолчанию, при попытке доступа к элементам такой коллекции, например при вызове методов: get(), iterator() или size(), заружаются сразу все элементы коллекции.

Для @Basic, @OneToMany и @OneToOne по умолчанию fetch=FetchType.EAGER. Подобное поведение, которое является предпочтительным в большинстве случаев, также может быть переопределено.

Загрузка по требованию примитивных полей класса, с использованием @Basic(fetch=FetchType.LAZY), применяется редко, и может быть полезна только в том случае, когда сущность имеет большое количество полей (порядка нескольких сотен), поэтому некоторые JPA провайдеры такой возможности не предоставляют.

Часто одна и та же сущность может использоваться в различном контексте, поэтому задавать жестко стратегию загрузки может быть не выгодно. В этом случае, для более тонкой конфигурации загрузки по требованию можно использовать механизмы языка JPQL.

В рамках JPQL запроса можно переопределить стратегию загрузки для определенного набора полей с LAZY на EAGER при помощи:

· оператора “.” (точка).

· выражения JOIN и JOIN FETCH.

Оператор JOIN FETCH в этом смысле является стандартным средством JPQL для предварительной инициализации коллекций и свойств.

Используя JOIN FETCH нужно всегда учитывать, что когда JPQL запрос преобразуется в SQL, оператор JOIN FETCH преобразуется в INNER JOIN, что в некоторых случаях может быть неприемлемо. Например, в том случае, если сущности соответствует какая-либо пустая коллекция, попытка использовать JOIN FETCH приведет к ошибке, потому как INNER JOIN не вернет ни одного результата. Чтобы избежать такой ситуации необходимо использовать в таких случаях LEFT JOIN FETCH, который при трансляции в SQL преобразуется соответственно в LEFT JOIN.

3.2 Hibernate, EclipseLink и OpenJPA

В рамках конкретного JPA провайдера спектр возможностей по оптимальной конфигурации загрузки по требованию в рамках доменной модели много шире. Кроме тех средств, что описаны в предыдущем параграфе, можно использовать:

? особый тип загружаемых по требованию коллекции (extra-lazy коллекции)

? несколько групп свойств, загружаемых вместе (fetch groups)

? пакетную выборку (batch fetch)

3.2.1 Extra-lazy коллекции

Hibernate предлагает особый тип загружаемых по требованию коллекций, которые инициализируются не целиком, а поэлементно. Таким образом, в большинстве случаев, при обращении к методам этих коллекций, Hibernate не будет пытаться загрузить из БД всю коллекцию, только ее отдельные элементы. Это может быть удобно для больших коллекций, загрузка которых целиком может сильно увеличить потребление памяти, и будет только во вред для коллекций небольшого размера, потому как для выполнения каждого метода будет осуществляться отдельный (один или более) SQL запрос. Единственным методом, при обращении к которому такая коллекция будет загружена целиком - это метод iterator(). В Hibernate реализация такого типа коллекций существует для любого базового интерфейса коллекций. Чтобы использовать Extra-lazy коллекции соответствующее поле коллекции должно быть помечено как @LazyCollection(LazyCollectionOption.EXTRA).

Подобный механизм, также предоставляет и OpenJPA при использовании аннотации @LRS (аббревиатура large result set). Однако в этом случае на такие поля накладываются серьезные ограничения. Одним из них является то, что с @LRS могут использоваться только коллекции типа java.util.Collection и java.util.Map, более того, тип элементов этих коллекций не может быть конкретизирован.

3.2.2 Fetch groups

При использовании механизма Fetch groups для каждого типа сущности можно задать несколько групп свойств (которые могут пересекаться), значения которых будут загружаться из базы данных вместе. Для каждой такой группы можно задать имя и использовать его на уровне запросов к интерфейсу EntityManager конкретного JPA провайдера. Таким образом, можно более тонко, настроить механизм загрузки по требованию, с учетом основных вариантов использования сущностей одного типа, при определенных условиях.

Кроме того правила загрузки, в рамках Fetch groups можно задать таким образом, что при загрузке по требованию одного свойств сущности вместе с ним будет загружена определенная, ассоциированная с ним группа других свойств этой же сущности.

Механизм Fetch groups доступен в OpenJPA и EclipseLink. В Hibernate схожий функционал предоставляется с использованием Fetch profiles.

3.2.3 Пакетная выборка

Пакетная выборка (batch fetch, по аналогии с batch processing, пакетной обработкой) является еще одним средством оптимизации механизма загрузки по требованию. При использовании пакетной выборки за один запрос из базы данных считывается сразу несколько однотипных объектов. В ряде случаев это может серьезно увеличить производительность системы, снизив количество запросов к базе данных.

Рис.4. Обычная загрузка по требованию

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Рис.5. Загрузка по требованию с использованием пакетной выборки

фреймворк шаблон код выборка

Особенно важно то, что этот механизм полностью прозрачен для разработчика и может использоваться для любых типов связей (OneToOne, ManyToOne, OneToMany, ManyToMany, ElementCollection).

Механизм пакетной выборки доступен только в Hibernate и EclipseLink.

В Hibernate для использования пакетной загрузки соответствующее поле помечается аннотацией @BatchSize, а в качестве ее обязательного атрибута size задается объем выборки. В рамках работы было обнаружено, что Hibernate ограничивает этот объем до 128, чтобы оградить разработчика от проблем связанных с потреблением большого объема памяти.

При использовании EclipseLink может быть организовано целых три типа пакетной выборки:

· BatchFetchType.JOIN

· BatchFetchType.EXISTS

· BatchFetchType.IN

Все они задаются в контексте аннотации @BatchFetch.

На практике было обнаружено, что в том случае, когда используется BatchFetchType.IN, поведение EclipseLink максимально приближено к Hibernate. Только в рамках этого конкретного типа будет учитываться второй атрибут аннотации @BatchFetch - size, который также как и в случае Hibernate определяет объем пакетной выборки. Однако в противовес Hibernate этот объем оказался не ограничен сверху, по умолчанию его значение 256 и не может быть меньше.

Механизм пакетной выборке очень важен в контексте проблемы n+1 запросов, который рассматривается далее.

4. ПРОБЛЕМЫ ЗАГРУЗКИ ПО ТРЕБОВАНИЮ

Загрузка по требованию - это палка о двух концах. Не смотря на то, она помогает добиться лучших результатов (производительности при работе с БД) в одних случаях, в остальных - ее использование может принести значительный вред. В то время как использование загрузки по требованию сокращает объем излишних данных, загружаемых из БД (как и количество запросов с использованием JOIN), такое поведение может обернуться серьезными проблемами, если все эти данные в конечном итоге оказываются необходимы. Однако это не причина отказываться от загрузки по требованию. Эта проблема, которая часто неправильно толкуется разработчиками, решение которой, во много зависит от контекста. [7, c. 137]

4.1 Проблема N+1 запросов

Проблема N+1 запросов в контексте загрузки по требованию является наиболее существенной и широко распространенной. [7, c. 137] Понять причины и суть проблемы поможет следующий пример: предположим, что в рамках некоторой модели имеется всего два класса, представляющие студента (Student) и студенческую группу (Group):

@Entity

class Student {

@Id int id;

String name;

String surname;

String patronymic;

... // get и set методы

}

@Entity

class Group {

@Id int id;

String name;

@OneToMany

Set<Student> students;

... // get и set методы

}

В этом примере принципиально то, что коллекция в поле students класса Group будет загружаться только по требованию, так как по умолчанию для @OneToMany атрибут fetch принимает значение FetchType.LAZY.

Допустим теперь, что необходимо вывести список всех групп и их участников.

Для этого, сначала получим список всех групп, обратившись к интерфейсу EntityManager:

Set<Group> groups = entityManager

.createQuery(“SELECT g FROM Group g”)

.getResultList();

Далее необходимо в цикле обойти все группы, и их участников и вывести их имена:

for(Group g : groups) {

out.println(g.getName());

for(Student s : g.getStudents()){

out.println(“ “

+ s.getSurname()

+ s.getName()

+ s.getPatronymic());

}

}

Здесь мы и столкнемся с проблемой N+1 запросов. В данном конкретном случае N - это число групп. Таким образом, в ходе выполнения этого кода будет сделано N+1 запросов к базе данных: один - на загрузку из базы данных списка групп, и по одному на каждую группу, чтобы загрузить ее список студентов. В итоге, если количество групп составит 100, всего будет выполнен 101 запрос к базе данных. Такое поведение крайне неэффективно, если учесть, что работа с БД - это узкое место почти любого веб приложения.

У этой проблемы нет универсального решения (иначе бы оно уже стало частью спецификации JPA). В каждой конкретной ситуации требуется свой индивидуальный подход. Однако для того, чтобы свести к минимуму тот урон, что наносит описанная проблема, есть несколько устоявшихся стратегий. Их общая цель - сократить количество SQL запросов и попытаться организовать загрузку всех необходимых данных максимально эффективно. [7, c. 139]

Во-первых, работая в рамках JPA, можно в простейшем случае отказаться от ленивой загрузки для коллекции students.

@Entity

class Group {

...

@OneToMany(fetch=FetchType.EAGER)

Set<Student> students;

...

}

При этом для получения списка групп и соответствующих им студентов

Hibernate и OpenJPA используют один SQL запрос с JOIN, а EclipseLink - два, для получения групп и студентов отдельно. Однако, используя средства конкретных JPA провайдеров это поведение можно тонко настроить.

Во-первых, EclipseLink также будет использовать JOIN в своих запросах, при наличии аннотации @JoinFetch. Таким образом, каждый из JPA провайдеров может ограничиться всего одним запросом для обеспечения нормальной работы кода, представленного ранее.

Во-вторых, при наличии аннотации @Fetch(FetchMode.SUBSELECT), Hibernate будет вместо JOIN использовать некореллируемый подзапрос.

@Entity

class Group {

...

@Fetch(FetchMode.SUBSELECT)

@OneToMany

Set<Student> students;

...

}

В этом случае, будет выполнено два запроса (вместо 101 по умолчанию), один - для получения всех групп, другой (с подзапросом) - для получения их тестов.

Таким образом, проблема N+1 запросов успешно решается с использованием предварительной загрузки, однако не стоит отказываться от загрузки по требованию так скоро. Для коллекций загрузка по требованию является предпочтительной стратегией в большинстве случаев, и чтобы отказаться от нее у разработчика должны быть на то серьезные причины.

Лучшим, в контексте решения проблемы N+1 запросов, является подход, при котором по умолчанию используется загрузка по требованию, но в отдельных частных случаях, когда это действительно необходимо, ее сменяет предварительная загрузка. [7, c.140]

Чтобы переопределить поведение провайдера в конкретной ситуации можно использовать механизм JOIN FETCH предоставляемый в рамках JPQL:

Set<Group> groups = entityManager.createQuery(

“SELECT g FROM Group g JOIN FETCH g.students”

).getResultList();

При этом коллекция students остается без изменений:

...

@OneToMany

Set<Student> students;

...

Таким образом, список студентов для каждой группы будет загружен предварительно только в данном конкретном случае.

Когда писать отдельный JQPL запрос для каждой ситуации слишком накладно, в случае Hibernate и EclipseLink можно использовать механизм пакетной выборки. Он, с одной стороны, прозрачен для разработчика, а с другой, помогает сократить количество SQL запросов до разумных пределов (хоть и не до минимума). Так в случае Hibernate используется:

...

@BatchSize(size=50)

@OneToMany

Set<Student> students;

...

С учетом исходного JPQL запроса:

Set<Group> groups = entityManager

.createQuery(“SELECT g FROM Group g”)

.getResultList();

если учесть, что количество групп составляет 100 штук, то вместо 101 запроса можно получить всего 3. Один - на выборку всех групп и два - на выборку соответствующих им студентов. Когда @BatchSize(size=100)

количество запросов сокращается до двух.

Того же эффекта можно с EclipseLink можно достичь за счет:

...

@BatchFetch(BatchFetchType.IN)

@OneToMany

Set<Student> students;

...

В этом случае будет также необходимо всего два запроса, потому как по умолчанию для EclipseLink значение size=256.

5. ОЦЕНКА ЭФФЕКТИВНОСТИ РАЗЛИЧНЫХ СПОСОБОВ ОПТИМИЗАЦИИ ЗАГРУЗКИ ПО ТРЕБОВАНИЮ

На отдельных примерах сложно оценить, как различные особенности применения загрузки по требованию в Hibernate, EclipseLink и OpenJPA отражаются на архитектуре современных веб-приложений.

5.1 Демонстрационное приложение “Тестер”

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

5.1.1 Функциональные требования

При проектировании приложения учитывались три основных момента:

1) Создавать и редактировать тесты могут исключительно преподаватели. Студенты, в свою очередь, могут тесты проходить, и просматривать результаты своего тестирования.

2) Для организации тестовых заданий внутри приложения они группируются по категориям. В каждой категории может быть несколько тестов, также как и каждый тест может принадлежать к нескольким категориям. Добавлять, редактировать и удалять категории могут только преподаватели.

3) Каждый тест может состоять из вопросов как минимум двух типов:

? на выбор одного из представленных вариантов ответа

? на ввод определенного текстового (или числового) значения

5.1.2 Архитектура приложения

Приложение Тестер разрабатывалось в рамках стандартной многослойной (multilayered, не путать с multi-tier - многоуровневой/многозвенной) архитектуры. Ее схема представлена на рис. 6.

Рис.6. Архитектура приложения.

В основе каждого из этих слоев лежат проверенные временем шаблоны проектирования необходимые для построения надежной и простой для поддержания архитектуры. [7, с.5]

В рамках данной архитектуры для каждого слоя четко определен круг его обязанностей. Подробнее о каждом слое можно прочитать в [1] и [7].

5.1.3 Доменная модель приложения

Доменная модель (domain model) - это не что иное как модель предметной области приложения. В рамках даже такого относительно небольшого приложения, эта модель довольно объемна. На рис. 7 представлена ее диаграмма классов.

Рис.7. Диаграмма классов приложения “Тестер”

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

Наибольший интерес в дальнейшем будут представлять классы пользователя (User) и теста (Test):

@Entity @Table(name="USERS")

public class User implements Serializable {

@Id

long id;

String login, password, name, surname, patronymic;

boolean teacher;

@OneToMany(mappedBy="addedBy")

List<Test> tests;

@OneToMany(mappedBy="addedBy")

List<Category> categories;

...

}

@Entity

public class Test implements Serializable {

@Id

Long id;

String name, description, notes;

@ManyToOne

User addedBy;

@OneToMany(mappedBy="test")

List<Question> questions;

@ManyToMany

List<Category> categories;

...

}

Большая часть кода в этих листингах опущена для краткости.

5.1.4 Страница преподавателя

Чтобы дать количественную оценку для каждого из рассмотренных ранее методов оптимизации загрузки по требованию, была выбрана одна из страниц приложения, а именно страница преподавателя.

На этой странице представлены:

1) ФИО преподавателя

2) Список всех добавленных преподавателем категорий

3) Список всех добавленных преподавателем тестов

4) Список категорий, для каждого из тестов.

Рис.9. Страница преподавателя.

В запросе пользователя, для получения этой страницы, в том или ином виде, передается идентификатор преподавателя, соответствующий id пользователя рассмотренной ранее доменной модели. Контроллер, ответственный за страницу преподавателя, передает этот id слою сервисов, чтобы получить сущность пользователя с соответствующим идентификатором. Для этого id передается слою DAO и в конечном итоге методу getById() интерфейса EntityManager, который и вернет сущность пользователя с соответствующим идентификатором. Вернувшись в слой DAO, а потом и в слой сервисов, через контроллер сущность пользователя в конечном итоге попадает на JSP страницу.

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

...

${user.surnname} ${user.name} ${user.patronymic}

...

<c:forEach items="${user.tests}" var="test">

...

</c:forEach>

...

<c:forEach items="${user.categories}" var="category">

${category.name}

</c:forEach>

...

Таким образом, можно получить ФИО, список добавленных преподавателем категорий и тестов. Список категорий для каждого теста также можно получить, обратившись к одному из его свойств:

...

<c:forEach items="${user.tests}" var="test">

...

<c:forEach items="${test.categories}" var="tc">

${tc.name}

</c:forEach>

...

</c:forEach>

...

Нетрудно понять, что при этом, будет иметь место проблема N+1 запросов.

Для ее решения будут применены различные подходы, описанные в разд. 4.

5.2 Способы оптимизации

В качестве средств оптимизации будут рассмотрены:

1) предварительная загрузка (fetch=FetchType.EAGER)

2) пакетная выборка

3) JPQL запросы с использованием JOIN FETCH

Все они рассматривались ранее в разд. 4, однако стоит остановиться на каждом из них еще раз, чтобы подчеркнуть то, как тот или иной способ вписывается в контекст данного приложения.

Каждый из описанных способов будет рассматриваться применимо к полю categories класса test, рассмотренного ранее.

5.2.1 Предварительная загрузка

Предварительная загрузка в большинстве подобных случаев не является предпочтительной стратегией. Использование этого подхода редко может повысить общую производительность системы, однако это именно такой случай. Дело в том, что в рамках приложения Тестер, в 5 случаях из 6, когда используется тест, необходим и список его категорий. Таким образом, отказ от загрузки по требованию - осознанный выбор, который в теории должен решить проблему N+1 запросов. Более того, использование данного подхода абсолютно прозрачно для разработчика и потому никак не отражается на архитектуре приложения.

Чтобы использовать предварительную загрузку, нужно внести лишь небольшие поправки в код класса теста:

@Entity

public class Test implements Serializable {

...

@ManyToMany(fetch=FetchType.EAGER)

List<Category> categories;

...

}

5.2.2 Пакетная выборка

Пакетная выборка совмещает в себе лучшие качества как предварительной загрузки, так и загрузки по требованию. Особенности ее использования уже описаны ранее. Стоит оговорить лишь то, что в случае Hibernate при использовании пакетной выборки не получится инициализировать за раз более 128 связей. Значения атрибута size большие 128 при использовании @BatchSize будут игнорироваться.

@Entity

public class Test implements Serializable {

...

@BatchSize(size=128)

@ManyToMany

List<Category> categories;

...

}

Для EclipseLink такого ограничения нет. По умолчанию BatchFetch(BatchFetchType.IN) инициализирует 256 связей. При использовании BatchFetch(BatchFetchType.JOIN) или BatchFetch(BatchFetchType.EXIST) в данном конкретном контексте будут загружены все категории для каждого теста разом.

@Entity

public class Test implements Serializable {

...

@BatchFetch(BatchFetchType.IN)

@ManyToMany

List<Category> categories;

...

}

Аналогично для BatchFetch(BatchFetchType.JOIN) и BatchFetch(BatchFetchType.EXIST)

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

5.2.3 JPQL запросы с использованием JOIN FETCH

Использование любого из вышеописанных методов никак не затрагивает бизнес-логику приложения и абсолютно прозрачно по отношению к разработчику. Использование специфического JPQL запроса для каждого отдельного случая в противовес этому все же оставляет небольшой след как на слое DAO, так и на слое сервисов, а при использовании @NamedQuery еще и в классах сущностей.

Чтобы решить проблему N+1 запросов, в данном конкретном случае применяется отдельный JPQL запрос, чтобы выбрать все тесты вместе с соответствующими им категориями:


Подобные документы

  • Архитектура и технология функционирования системы. Извлечение, преобразование и загрузка данных. Oracle Database для реализации хранилища данных. Создание структуры хранилища. Механизм работы системы с точки зрения пользователя и с точки зрения платформы.

    курсовая работа [2,2 M], добавлен 22.02.2013

  • Выявление сущностей и связей, атрибутов сущностей и назначение первичных ключей при разработке базы данных. Реляционная модель данных. Описание стадий жизненного цикла информационной системы: анализ, проектирование, реализация, внедрение, сопровождение.

    курсовая работа [152,2 K], добавлен 11.05.2014

  • Анализ объектно-ориентированного программирования, имитирующего способы выполнения предметов. Основные принципы объектно-ориентированного программирования: инкапсуляция, наследование, полиморфизм. Понятие классов, полей, методов, сообщений, событий.

    контрольная работа [51,7 K], добавлен 22.01.2013

  • Средства программирования, описание языка С++. Назначение программы, требования к документации. Стадии разработки, виды испытаний. Используемые технические, программные средства. Вызов и загрузка, входные и выходные данные. Программа и методика испытаний.

    контрольная работа [205,3 K], добавлен 23.07.2013

  • Проверка существования и статуса файла. Определение его размера. Открытие файла для чтения, записи, добавления. Закрытие файловых дескрипторов. Запись из переменной в файл. Загрузка файла из сети. Создание и удаление каталога. Функции работы с каталогами.

    презентация [133,9 K], добавлен 21.06.2014

  • Требования, предъявляемые к инфологической модели, ее компоненты. Построение модели и диаграммы "объект — свойство — отношение". Три типа бинарных связей. Подтипы и супертипы сущностей в языках программирования. Каскадные удаления экземпляров сущностей.

    лекция [404,3 K], добавлен 17.04.2013

  • Информационный анализ и выявление основных сущностей предметной области. Определение взаимосвязей сущностей. Построение концептуальной модели. Логическое моделирование базы данных "Компьютерный мир". Технология сбора, передачи и обработки информации.

    курсовая работа [1,9 M], добавлен 13.02.2014

  • Общее определение и история создания JavaScript библиотек, их назначение и использование. Виды и особенности JS фреймворков. Создание клиентского приложения (каталога комплектующих компьютера), написание кода страницы с использованием фреймворка Jquery.

    курсовая работа [544,5 K], добавлен 17.12.2013

  • Общая схема процесса загрузки, его основные этапы и отличительные особенности. Первичный и вторичный загрузчики, их функциональные возможности и направления действия. Места возможного размещения загрузочного сектора. Требования к дисковому пространству.

    презентация [86,1 K], добавлен 20.12.2013

  • Обследование предметной области. Проектирование реляционной базы данных: описание входной и выходной информации, перечень сущностей и атрибутов, создание модели, выбор ключей. Разработка и обоснование представлений для отображения результатов выборки.

    курсовая работа [539,0 K], добавлен 12.12.2011

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.