Запросы 1
Обзор Решение задач поиска информации Обзор возможностей Java Persistence QL Определение запроса Динамический запросы Named запросы Параметры запроса Выполнение запроса Повторное использование запроса
Обзор Выполнение запроса Оптимизация read-only запросов Особые типы возвращаемых значений Постраничный вывод (pagination) Запросы и незафиксированные изменения (uncommitted changes) Bulk Update и Delete. Ограничения Подсказки (Query Hints) Лучшие практики по использованию запросов
Решение задач поиска информации Задачи, требующие возможности использования запросов: Поиск Сортировка Аналитика и business intelligence Перемещение данных из БД в приложение Существует необходимость использования запросов в терминах приложения – в терминах доменных объектов, а не в терминах SQL – таблиц, ключе и т.д. Java Persistence API поддерживает 2 языка запросов: JPQL (главная возможность) SQL (тоже может использоваться) 4
Обзор возможностей Java Persistence QL Родился из EJB QL, но существенно расширяет его Проектировался быть максимально похожим на SQL Ключевые возможности: Одиночные и множественные выборки Агрегатные функции, сортировки, группировки Более понятный JOIN синтаксис Условия и подзапросы Update и delete bulk данных Выборка не объектных данных (projection) Синтаксис и изучение этих и других возможностей JPQL будет представлен в отдельном блоке 5
Примеры JPQL Filtering Results SELECT e FROM Employee e WHERE e.department.name = 'NA42' AND e.address.state IN ('NY','CA') Projecting Results SELECT e.name, e.salary FROM Employee e Joins между сущностями SELECT p.number FROM Employee e JOIN e.phones p WHERE e.department.name = 'NA42' AND p.type = 'Cell' Агрегатные запросы SELECT d, COUNT(e), MAX(e.salary), AVG(e.salary) FROM Department d JOIN d.employees e GROUP BY d HAVING COUNT(e) >= 5 Параметры запросов SELECT e FROM Employee e WHERE e.department = ?1 AND e.salary > ?2 6
Определение запроса Запрос может быть определен двумя способами: Динамически в коде приложения Статически в persistence unit в xml или с помощью аннотаций. Доступ к запросу по имени 7
Динамический запрос Создается динамически вызовом em.createQuery Провайдер будет парсить каждый динамический запрос. Низкий performance. Провайдер может оптимизировать (кэшировать) одинаковые запросы Совсем плохо клеить весь запрос (проблемы и performance, и security): 8
Динамический запрос. Использование параметров Использование parameter binding Это позволит и провайдеру, и БД кэшировать запросы (не парсить каждый раз) Может повысить производительность в сотни раз! 9
Named запросы Более предпочтительный способ организации запросов Провайдер может сделать pre-parse запросов Named запрос определяется которая помещается на уровне класса сущности Named запрос является видимым в пределах persistence unit, где имя этого запроса должно быть может объединять несколько запросов Запросы можно объявлять в XML. Возможно, это более предпочтительный вариант Пример. Named запросы (example_06-01) 10
Параметры запроса Параметр может быть: Именованным :name Query.setParameter(String,Object) Нумерованным ?1 (начиная с 1) Query.setParameter(Integer,Object) Значением параметра может быть: Объект, значение которого необходимо Сущность! При генерации SQL будет выбран первичный ключ сущности Пример. Named запросы (example_06-02) 11
Выполнение запроса Существует 3 способа выполнения запроса, в зависимости от типа возвращаемого значения: getSingleResult() когда уверены, что вернется ровно 1 результат (count(*)). Возбуждает NoResultException, если элемента не нашлось Возбуждает NonUniqueResultException, если элемента не единственный getResultList() Возвращает пустую коллекцию или List, хранящий сортированную выборку executeUpdate() – выполнение bulk update операций 12
Повторное использование запроса Запрос может быть повторно использован, если PC в котором он был создан, активен: Для transaction-scoped EM это означает ту же транзакцию Для extended и application EM – пока EM не закрыт 13
Выполнение запроса Запрос может возвращать список сущностей Может возвращать другие типы (Basic, primitives) Сущности, возвращенные запросом будут managed в том PC, в котором выполнялся этот запрос Если запрос выполнялся вне транзакции в transaction- scoped EM, возвращенные сущности будут detached Как следствие – PC может разбухать Рассмотреть очистку контекста - em.clear() 14
Оптимизация read-only запросов Провайдеру необходимы время и память, для того, чтобы сделать сущность managed Для тех сущностей, являющихся результатом выполнения запроса, для которых модификация не потребуется, необходимо предотвратить создание managed сущности Для этого запрос нужно выполнять в NOT_SUPPORTED методе 15
Особые типы возвращаемых значений Массив объектов возвращается, когда запрос содержит более одного выражения в условии SELECT Можно создавать custom объект, инкапсулирующий результаты запроса с помощью оператора NEW Пример. Special results type (example_06-03) 16
Постраничный вывод (pagination) Постраничный вывод позволяет выбирать нужный объем данных, в случае, если общий объем велик Для указания начала выборки используется query.setFirstResult(), для указания объема выборки используется query.setMaxResults() JPA провайдер реализует это логическую возможность физически по-разному, в зависимости от поддержки БД и типа JDBC драйвера Хорошая мысль посмотреть, как именно реализована эта возможность в вашем случае Пример. Pagination (example_06-04) 17
Запросы и незафиксированные изменения (uncommitted changes) При выполнении операций над сущностями (find, persist, etc) EM может корректно отследить их состояние и вернуть нужную сущность из PC C запросами сложнее, так как они транслируются напрямую в SQL Запросу могут потребоваться данные, которые логически уже persisted (updated и т.д.), но EM еще не сделал их сброс (flush) в БД. SQL не найдет эти данные Провайдер обеспечивает правильную работу запросов!: Выбирает данные из БД, если он уверен, что это безопасно Делает flush PC перед выполнением SQL выражения JPQL запроса Пример. Flush не эффективен! (example_06-05) 18
Запросы и незафиксированные изменения (uncommitted changes) Для обеспечения большего контроля над способом обеспечения целостности данных (integrity) при выполнении запроса, интерфейсы EntityManager и Query имеют метод setFlushMode(AUTO|COMMIT) Режим COMMIT говорит провайдеру не вмешиваться в обеспечение целостности Обычно это означает не сбрасывать данные в БД Установка параметра на EM означает распространение значения параметра на все запросы Альтернативно можно установить параметр на специфический запрос 19
Bulk Update и Delete Для изменения набора сущностей, без индивидуальной загрузки и обновлении каждой сущности называется bulk update (delete) В отличии от SQL такие запросы должны учесть все возможные комбинации мэпингов, соответственно существуют некоторые ограничения Пример. Bulk update сущности (example_06-06) 20
Bulk Update и Delete. Ограничения Оператор UPDATE или DELETE должен выполняться только над одной сущностью Операция должна модифицировать либо простые атрибуты, либо single-valued ассоциации Изменения не отражаются на persistence context Поэтому изменения должны происходить в другой транзакции Использование bulk запросов совместно с extended PC потенциально опасно, т.к. сущности в контексте не узнают об изменениях в БД 21
Bulk Update и Delete. Сценарий потери данных 1.Транзакция началась 2.Сущность А создается вызовом метода persist() 3.Сущность В получается из БД вызовом find() и модифицируется приложением 4.Происходит bulk DELETE сущности А 5.Происходит bulk UPDATE сущности В 6.Транзакция завершается (commit) Изменения с шагов 4 и 5 перетрутся действиями с шагов 2 и 3 – не то, что подразумевало приложение 22
Bulk Delete. Дополнительные ограничения Операция DELETE не продляется (cascade) на соответствующие сущности Провайдер никак не контролирует constraints БД Например, попытка удалить сущность, являющуюся target сущностью owing сущности Пример (возбудится PersistenceException): DELETE FROM Department d WHERE d.name IN ('CA13', 'CA19', 'NY30') Мы должны вначале обнулить ссылки на этот департамент: UPDATE Employee e SET e.department = null WHERE e.department.name IN ('CA13', 'CA19', 'NY30') Важно! Проектировать БД с использованием constraints 23
Подсказки (Query Hints) Можно указывать провайдеру подсказки (hints), влияющие на выполнение запроса Подсказки не специфицированы в JPA – изучите документацию своего провайдера! 24
Лучшие практики по использованию запросов Named запросы Способствует использованию именованных параметров (security, performance) Придумайте соглашение о именовании запросов, т.к. имя запроса должно быть уникально Named запросы могут быть переписаны на SQL без модификации клиентского кода Read-only запросы (отчетность) Вызывайте запрос так, чтобы не создавались managed сущности Выбирайте только часть данных (projected queries), а не всю сущность целиком 25
Лучшие практики по использованию запросов Используйте подсказки Они могут служить основным инструментом улучшения производительности системы Лучше хранить их в XML. Подмена провайдера Использование stateless бинов (transaction- scoped EM) при использовании запросов Использование бизнес имени метода бина, вместо внутреннего системного имени запроса Оптимизация запросов в связи с использованием detached сущностей, отсутствие необходимости выполнять запрос в транзакции 26
Лучшие практики по использованию запросов Bulk Update и Delete Должны выполняться в отдельной транзакции Должны выполняться с осторожностью и пониманием. В узком круге задач Изучите возможные ограничения вашего провайдера Изучите ваш провайдер Изучите SQL, который генерирует ваш провайдер. Это не специфицировано JPA Стратегия paginating запросов Стратегия сброса данных в БД при выполнении запроса 27