Глава 5 Spring in Action 2th edition |
Введение
Теперь пришло время включить ваши знания об основах Spring в реальное приложение. Лучше всего начать с такого требования любого корпоративного приложения, как сохранение данных. Каждый, возможно, уже имел дело с базами данных в прошлом. При этом вы знаете, что доступ к данным имеет много "подводных камней". У нас есть framework для инициализации нашего доступа к данным, открытия соединений, обработки различных исключений и закрытия соединений. Если бы у нас этого небыло, то мы могли бы испортить или удалить важные данные. Даже, в случае если Вы не испытывли последствий неправильно доступа к данным, то поверьте на слово, это - плохая штука.
Так как мы стремимся к "хорошим штукам", мы обращаемся к Spring. У Spring есть набор модулей доступа к данным, которые интегрируются с разными популярными технологиями хранения. Если вы для сохранения данных используете напрямую JDBC, iBATIS или ORM (напрмер Hibernate), Spring избавит от написания множества рутинного кода обычно требуемого при доступе к данным. Вместо возни с низкоуровневым доступом к данным можно положиться на Spring, который выполнит эту работу за Вас, а основное свое внимание уделить управлению данными в самом приложении.
В этой главе мы собираемся построить слой persistence для приложения RoadRantz (см. рисунок 5.1). В этом слое мы сталкиваемся с некоторым выбором. Мы могли бы использовать JDBC, Hibernate, the Java Persistence API (JPA), iBATIS или любой другой из числа persistence frameworks. К счастью для нас, Spring поддерживает все эти persistence механизмы.
По мере токо, как мы строим persistence слой приложения RoadRantz, мы увидим, как Spring использует абстрактные функции доступа к данным, что упрощает сохранение кода. Вы увидите, как Spring позволяет работать с JDBC, Hibernate, JPA, и iBATIS еще проще. И прежде чем мы закончим наше обсуждение доступа к данным, мы поговорим о том, как использовать Spring поддержку декларативного кэширования чтобы увеличить производительность приложения.
[Рисунок 5.1 Как и большинство приложений, RoadRantz сохраняет и восстанавливает данные из реляционных баз данных. Это persistence слой приложения, где происходит весь доступ к данным]
Вне зависимости от persistence технологии, которую вы выбрали, простую JDBC или сложную JPA, есть много точек соприкосновения между всеми frameworks доступа к данным в Spring. Поэтому, прежде чем мы перейдем к поддержке доступа к данным в Spring, давайте поговорим об основах DAO поддержки в Spring.
Source(s): Глава 5 Spring in Action 2th edition
Философия доступа к данным в Spring
Из предыдущих глав Вы знаете, что одна из целей Spring - позволить Вам разрабатывать приложения, соответствующие объектно-ориентированным (ОО) принципам программировани, основанные на интерфейсах. Поддержка доступа к данным в Spring не является исключением.
DAO обозначает "объект доступа к данным", что прекрасно описывает роль DAO в приложении. DAO существуют, чтобы обеспечить средства для чтения и записи данных в базу данных. Они должны предоставлять эту функциональность через интерфейс, который доступен остальной части приложения. Рисунок 5.2 показывает правильный подход к проектированию вашего уровня доступа к данным.
Как Вы можете видеть, сервисные объекты получают доступ к DAOs через интерфейсы. Это имеет несколько преимуществ. Во-первых, это делает Ваши сервисные объекты легко тестируемыми так как они не связаны с конкретной реализации доступа к данным. На самом деле, можно создать макет реализаций этих интерфейсов доступа к данным. Это позволит вам проверить свои сервисные объекты без необходимости подключения к базе данных, что значительно ускорит Ваш модуль тестов и исключит вероятность ошибки теста из-за противоречивых данных.
Кроме того, слой приложения реализуюший доступ к данным превращает persistence в "черный ящик" . То есть, подход DAO состоит в "изоляции" выбранного persistence , когда только ключевые методы доступа к данным предоставляются через интерфейс. Это делается для получения гибкого дизайна системы и позволяет заменить используемый persistence фреймворк на другой с минимальными воздействием на остальной код приложения. Если детали реализации уровня доступа к данным будут просачиваться в другие части приложения, то все приложение станет связанным с уровнем доступа к данным, что приведет к жесткой конструкции системы.
ПРИМЕЧАНИЕ.
- Если после прочтения двух последних разделов, вы чувствуете, что у меня сильный уклон в сторону сокрытия слоя persistence за интерфейсами, то я счастлив, что смог сформировать у Вас эту точку зрения. Дело в том, я считаю, что интерфейсы являются ключевыми к написанию слабосвязанного кода и что они должны быть использованы во всех слоях приложения, а не только на уровне доступа к данным. Тем не менее, это также важно отметить, что в то время как Spring поощряет использование интерфейсов, Spring не требует этого - вы можете использовать Spring для соединения beans (DAO или иным образом) непосредственно в свойства другого bean без интерфейса между них.
Один из способов Spring поможет вам оградить ваш уровень доступа к данным от остальной части вашего приложения, предоставляя вам последовательную иерархию исключений того, что используется во всех ее DAO модулях.
Source(s): Глава 5 Spring in Action 2th edition
Знакомство с иерархией исключений доступа к данным в Spring .
Если вы когда нибудь писали код JDBC (без использования Spring), то вы вероятно хорошо знаете что в JDBC и шагу нельзя ступить без необходимости отлова java.sql.SQLException. Вот некоторые общие проблемы, которые могут вызывать SQLException:
- приложение не может подключиться к базе данных.
- выполняемый запрос имеет синтаксические ошибки.
- таблицы и/или столбцы, упомянутые в запросе, не существуют.
- попытка вставки или обновления значений, которые нарушают ограничения базы данных.
Большинство SQLExceptions, которые выброшены означают фатальную ошибку. Если приложение не может подключиться к базе данных, poundtopockett.co.uk это обычно означает что приложение не может продолжать работу. Например, если ошибка в запросе, трудно что-то сделать с этим во время выполнения.
Если нет ничего, что можно сделать, чтобы избавиться от SQLException, почему мы должны перехватывать их?
Даже если у вас есть схема для решения некоторых SQLExceptions, вам придется перехватить SQLException и разбираться в его свойства для получения дополнительной информации о характере проблемы. Это потому, что SQLException рассматривается как "одна форма подходит всем" исключениям для проблем, связанных с доступом к данным. Вместо того, чтобы иметь разные типы исключений для каждой возможной проблемы, SQLException является исключением "на все случаи жизни" охватывающее большинство проблем доступа к данным.
Некоторые persistence frameworks предлагают более разнообразные иерархии исключений. Hibernate, например, предлагает около двух десятков различных исключений, каждое исключение ориентированно на конкретную проблему доступа к данным. Это делает возможным написать блоки перехвата конкретных исключений, с которые Вы хотите обрабатывать.
Но следует учесть, что Hibernate-исключения являются Hibernate-специфичными. А как было сказано выше, мы хотели бы изолировать специфику механизма сохранения в слое доступа к данным. Если бросаются конкретные Hibernate исключения, то факт того, что мы имеем дело именно с Hibernate будет просачиваться в остальные части приложения.
Таким образом, или с этим придется мириться, или Вы будете вынуждены перехватывая исключения используемого persistence провайдера, преобразовывать их в некие новые "универсальные", выбрасывая их повторно, чтобы получить желаемый эффект "черного ящика".
С одной стороны, иерархия исключений JDBC является слишком общей, для удобного анализ конкретных причин , возникающих проблем. С другой стороны, иерархия исключений в Hibernate - проприоретарна и Hibernate-специфична. Мы же нуждаемся в иерархиии исключений доступа к данным являющаейся информативной, но не связанная непосредственно с конкретным типом persistence провайдера.
Source(s): Глава 5 Spring in Action 2th edition
"Универсальная" иерархия исключений
Платформа Spring обеспечивает собственную иерархию исключений доступа к данным, позволяющую решить обе озвученные проблемы. В отличие от JDBC, Spring предоставляет несколько исключений доступа к данным, каждое из которых описывает причины того, почему они возникли. Таблица 5.1 показывает сравнение некоторых исключений доступа к данным Spring-а против исключений предлагаемых JDBC.
Как Вы можете видеть, исключения Spring описывают практически все, что может произойти во время чтения или записи данных в базу. А список исключений доступа к данным в Spring больше, нежели показано в таблице 5.1. Он не приводится целиком только потому, чтобы не вызвать, ощущение абсолютной неполноценности JDBC.
Таблица 5.1 Иерархия исключений JDBCв сравнении с исключениями доступа к данным в Spring.
Хотя иерархия исключений в Spring гораздо богаче, чем простые SQLException JDBC, она не связана с каким-либо частным решением persistence. Это означает, что Вы можете рассчитывать на то, что Spring бросает согласованный набор исключений, независимо от выбранного вами провайдера persistence. Это помогает держать Ваш выбор persistence в ограниченном слоем доступа к данным.
Source(s): Глава 5 Spring in Action 2th edition
Смотрите! Нет блоков перехвата!
Из таблицы 5.1, не очевидно что все эти исключения наследуются от DataAccessException. Но "особенным" DataAccessException делает тот факт, что оно является непроверяемым(uncheked) исключением. Другими словами, Вы не обязаны ловить каждое возникшее исключение доступа к данным используя Spring (хотя вы можете это сделать, если хотите).
DataAccessException это лишь частный пример многогранной философии Spring, проявляющийся в альтернативах проверяемых и непроверяемых исключений. Spring придерживается позиции, что многие исключения являются результатом проблем, неразрешимых в блоке catch. Вместо того, чтобы заставить разработчиков писать блоки обработки исключений (которые часто остаются пустыми), Spring способствует использованию непроверяемых исключений. Это позволяет оставить решение о перехватите исключений в руках самого разработчика.
Для того чтобы воспользоваться преимуществами исключений доступа к данным в Spring , Вы должны использовать один из поддерживаемых Spring-ом шаблонов доступа к данным. Давайте посмотрим, как Spring-шаблоны могут значительно упростить работу с данными.
Source(s): Глава 5 Spring in Action 2th edition
Шаблоны доступа к данным
Возможно, Вы когда-либо раньше путешествовали на самолете. Коль так, Вы наверняка согласитесь с тем, что одной из самых важных частей путешествия является перемещение вашего багажа из точки А в точку Б. В этом процессе довольно много шагов. Когда вы приходите к терминалу, то ваша первая остановка будет у стойки для проверки багажа. Далее, служба безопасности проверит его для обеспечения безопасности полета. Затем он принимается на "поезд для багажа" и движется к нужному самолету. Если вы используете пересадку с рейса на рейс, то и ваш багаж должен быть перемещен вслед за вами. Когда вы прибудете в пункт назначения, то багаж должен быть извлечен из самолета и размещен на транспортере. Наконец, вы спуститесь в зону получения багажа и заберете его.
Хотя существует множество шагов, выполнения этого процесса, вы активно задействованы лишь в паре из них. А перевозчик берет на себя ответственность за выполнение всех остальных необходимых действий. Лично, Вы участвуете лишь тогда, когда вам действительно нужно что-то сделать, об остальном же просто - "заботятся". Это отражает сущность мощного шаблона дизайна: Template Method.
Шаблонный метод определяет скелет процесса. В нашем примере, процесс осуществляется над багажом, при отправлении из одного города и прибытии в другой. Сам процесс - фиксирован, он никогда не меняется. Общая последовательность событий для обработки багажа происходит каждый раз одинаково: багаж принят, багаж погружен на самолет, и так далее. Некоторые этапы процесса являются фиксированными, то есть, какие-то шаги случаются тоже каждый раз. Когда самолет прибывает в пункт назначения, каждый предмет багажа выгружается и размещается на транспортере для выдачи багажа.
Однако, в определенные моменты, процесс делегирует свою работу подклассу, который заполняет некую реализацию конкретными деталями. Это переменная часть процесса. Например, обработка багажа начинается с проверки пассажирских вещей. Эта часть процесса всегда должно произойти в начале, так что его последовательность в этом процессе является фиксированной. Однако регистрация багажа для каждого пассажира - отличается, и реализация этой части процесса определяется пассажиром. В программном обеспечении условный, шаблонный метод - "делегатов" реализует интерфейс определенной части процесса. Различные реализации этого интерфейса определяют конкретные реализации этой части процесса.
Это тот же шаблон, что Spring применяет для доступа к данным. Независимо от того, какие технологии мы используем, требуются определенные шаги доступа к данным. Например, мы всегда должны получать подключение к нашему хранилищу данных и очищать ресурсы, при завершении. Это фиксированные шаги в процессе доступа к данным. Но каждый метод доступа к данным, мы пишем с некоторыми отличиями. Мы составляем запросы для различных объектов и обновляем данные по-разному. Это и есть переменные шаги в процессе доступа к данным.
Spring разделяет фиксированные и переменные части процесса доступа к данным на два различных класса: шаблоны и обратные вызовов. Шаблоны управляют фиксированной частью процесса, а пользовательский код доступа к данным обрабатывается в обратных вызовах. Рисунок 5.3 показывает обязанности обоих этих классов.
Как вы можете видеть на рисунке 5.3, классы Spring шаблонов управляют фиксированной частью доступа к данным - это управление транзакциями, управление ресурсами и обработку исключений. Между тем, специфика доступа к данным, относящаяся к вашему приложению - создание отчетности, связывание параметров и маршалинг набора результатов - обрабатываются в реализации обратного вызова. На практике это делается элегантным framework, поскольку вам не придется беспокоиться о вашей логики доступа к данным.
У Spring есть несколько шаблонов на выбор, в зависимости от вашего выбора persistence провайдера. Если вы используете напрямую JDBC, то вы будете использовать JdbcTemplate. Но если вы предпочитаете один из ORM провайдеров , то тогда возможно HibernateTemplate или JpaTemplate это более подходящий выбор. В таблице 5.2 перечислены все шаблоны Spring для доступа к данным и их назначение.
Table 5.2 Spring comes with several data access templates, each suitable for a different persistence
mechanism.
Класс шаблона (org.springframework.*) | Назначение шаблона… |
---|---|
jca.cci.core.CciTemplate | JCA CCI connections |
jdbc.core.JdbcTemplate | JDBC connections |
jdbc.core.namedparam.NamedParameterJdbcTemplate | JDBC connections with support for named parameters |
jdbc.core.simple.SimpleJdbcTemplate | JDBC connections, simplified with Java 5 constructs |
orm.hibernate.HibernateTemplate | Hibernate 2.x sessions |
orm.hibernate3.HibernateTemplate | Hibernate 3.x sessions |
orm.ibatis.SqlMapClientTemplate | iBATIS SqlMap clients |
orm.jdo.JdoTemplate | Java Data Object implementations |
orm.jpa.JpaTemplate | Java Persistence API entity managers |
orm.toplink.TopLinkTemplate | Oracle’s TopLink |
Как вы увидите, для использования шаблона доступа к данным достаточно просто включить его в файл с настройками, как обычный бин в контексте Spring, а затем связать его с DAO в вашем приложении. Или же вы можете воспользоваться преимуществом поддержки DAO классов в Spring для дальнейшего упрощения конфигурации DAO в вашем приложении. Прямое связывание шаблонов это хорошо, но Spring также предоставляет набор удобных базовых классов DAO, которые могут управлять шаблоном для Вас. Давайте посмотрим, как эти основанные на шаблонах классы DAO работают.
Использование классов поддержки DAO
Собственно сами шаблоны доступа это только часть модуля доступа к данным в Spring. Каждый шаблон также предоставляет удобные методы, которые упрощают доступ к данным без необходимости создания явной реализации обратного вызова. Кроме того, на вершине дизайна "шаблон-обратный вызов" , Spring предоставляет "классы поддержки" DAO, которые предназначены быть подклассами Ваших собственных классов DAO. Рисунок 5.4 иллюстрирует связь между классом шаблона , "классом поддержки" DAO и Вашей собственной реализацией DAO класса.
Позже, когда мы исследуем варианты индивидуальной поддержки доступа к данным в Spring, мы увидим, как классы поддержки DAO обеспечивают удобный доступ к шаблону класса, который они поддерживают. При написании в приложении собственной реализации DAO, Вы можете включить подклассом класса поддержки DAO, и вызвать метод поиска шаблона, чтобы иметь прямой доступ к лежащему в основе шаблону доступа. Например, если в Ваше DAO приложение включен подклассом, класс поддержки JdbcDaoSupport, то Вам нужно всего лишь вызвать метод getJdbcTemplate() для получения шаблона доступа JdbcTemplate и работать с ним.
Плюс, если вам нужен доступ непосредственно к базовой persistence платформе, каждый из классов поддержки DAO обеспечивает доступ к любому классу, который он использует, чтобы общаться с базой данных. Например, класс JdbcDaoSupport содержит метод getConnection() для работы непосредственно с JDBC подключением.
Подобно тому, как Spring предоставляет несколько реализаций классов шаблонов доступа к данным, он также обеспечивает нескольким классов поддержки DAO - по одному для каждого шаблона. Таблица 5.3 перечисляет классы поддержки DAO, которые идут вместе со Spring.
Таблица 5.3 Классы поддержки DAO Spring обеспечивают удобный доступ к своим соответствующим шаблонам доступа к данным.
DAO support class (org.springframework.*) | Provides DAO support for… |
---|---|
jca.cci.support.CciDaoSupport | JCA CCI соединения |
jdbc.core.support.JdbcDaoSupport | JDBC соединения |
jdbc.core.namedparam.NamedParameterJdbcDaoSupport | JDBC соединения с поддержкой именованных параметров |
jdbc.core.simple.SimpleJdbcDaoSupport | JDBC соединения с поддержкой Java 5 новшеств |
orm.hibernate.support.HibernateDaoSupport | Hibernate 2.x сессии |
orm.hibernate3.support.HibernateDaoSupport | Hibernate 3.x сессии |
orm.ibatis.support.SqlMapClientDaoSupport | iBATIS SqlMap клиенты |
orm.jdo.support.JdoDaoSupport | Java Data Object реализации |
orm.jpa.support.JpaDaoSupport | Java Persistence API менеджеры сущностей |
orm.toplink.support.TopLinkDaoSupport | Oracle’s TopLink |
Хотя Spring обеспечивает поддержку нескольких persistence провайдеров, однако не хватит места, чтобы охватить их все в этой главе. Поэтому, я собираюсь сосредоточиться на том, что я считаю самыми выгодными persistence вариантами и те, которые Вы, скорее всего будете использовать.
Мы начнем с основ JDBC доступа , так как это основной способ чтения и записи баз данных. Далее мы рассмотрим работу с Hibernate и с JPA , двумя самыми популярными ORM решения на базе POJO. В конце, я буду копаться в поддержке Spring для iBATIS , который является persistence провайдером, совмещающим поддержку отображения в стиле ORM с полным контролем запроса JDBC.
Но обо всем по порядку - большинство из вариантов в Spring persistence поддержки будут зависеть от источника данных. Итак, прежде чем мы сможем начать работу по созданию объектов шаблонов и DAO классов, нам предварительно нужно настроить сам Spring для работы с источником данных, чтобы обеспечить доступ DAO объектов к базе данных
Настройка источников данных
Независимо от того, какую из форм поддержки DAO в Spring Вы используете, Вам необходимо настроить ссылку на источник данных. Spring предлагает несколько вариантов настройки beans источников данных в Spring приложении, в том числе:
- Источники данных, которые определены с помощью драйвера JDBC
- Источники данных, найденные через JNDI
- Источники данных, из пулов соединений с БД
Для готовых к production приложений я рекомендую использовать источник данных который берет соединения из пула соединений. Когда это возможно, я предпочитаю получить объединенный источник данных из сервера приложений через JNDI. Исходя из этого предпочтения, давайте начнем изучение с того как настроить Spring для извлечения данных из источника JNDI.
Использование JNDI источников данных
Приложения созданные в Spring будут довольно часто разворачиваться для запуска в сервере JEE приложений, таких как WebSphere, JBoss, или даже веб-контейнер Tomcat. Эти серверы позволяют настраивать источники данных для получения их через JNDI. Преимущество настройки источников данных таким образом состоит в том, что ими можно полностью управлять вне приложения, оставив приложению только запросить доступ к источнику данных, когда это потребуется будет. Более того, источники данных, управляются сервером приложений и часто кластеризованы для более высокой производительности и могут заменяться системными администраторами.
С помощью Spring Вы можете настроить ссылку на источник данных так, чтобы он хранился в JNDI и вставлять ее в классы при необходимости, так словно это еще один обычный bean Spring. JndiObjectFactoryBean позволяет получить из JNDI любой объект (включая источники данных) и сделать его доступным как bean в Spring.
Мы рассмотрим подробнее JndiObjectFactoryBean когда перейдем к главе 11. Пока же будем просто использовать JndiObjectFactoryBean, извлекая данные из источника JNDI:
<bean id="dataSource"
class="org.springframework.jndi.JndiObjectFactoryBean"
scope="singleton">
<property name="jndiName" value="/jdbc/RantzDatasource" />
<property name="resourceRef" value="true" />
</bean>
Атрибут jndiName используется, чтобы определить имя ресурса в JNDI. Если только свойстов jndiName установлено, то источник данных будет найден в том виде как он есть. Но если приложение работает в сервере приложений Java, то Вы вероятно захотите установить свойство resourceRef в true.
Когда resourceRef равно true, то к значению jndiName будет добавляться java:comp/env/ для получения источника данных в качестве ресурса Java из JNDI каталога сервера приложений. Следовательно, фактическое имя будет java:comp/env/jdbc/RantzDatasource.
Проблемы доступа к JNDI
Использованиие JNDI источников данных имеет одну особенность, потенциально - проблемную. Она связана с тем что доступ к JDBC источникам в JNDI происходит через контекст сервера(не следует путать контекст JEE сервера ,c контекстом Spring, это - два совершенно разных понятия!). Легче всего проблема проявит себя, при попытках обратится к JNDI источнику данных, без развертывания приложения в сервере(например при unit-тестировании компонентов приложения вне сервера). Суть-же проблемы заключается в том, что хотя контекст сервера автоматически доступен любому компоненту развернутого в нем приложения, однако без принятия специальных мер, контекст будет недоступен этому же компоненту, но работающему вне - сервера. Таким образом доступ к JNDI источнику данных, извне сервера, становится - невозможным.
Наиболее простой и очевидный путь решения проблемы (в нашем случае), это явно инициализировать контекст JEE сервера, в XML-файле конфигурации Spring. Это делается путем установки значений(зависящих от конкретного JEE сервера) для свойства jndiEnvironment в бине JndiObjectFactoryBean. Свойство jndiEnvironment реализовано в виде карты типа java.util.Properties. В ней, properties-парами задаются параметры инициализации контекста для данного сервера. Простейший пример того как это делается для сервера Oracle WebLogic приведен ниже:
<bean id="dataSource"
class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="jdbc/mySQL1" />
<property name="resourceRef" value="true" />
<property name="jndiEnvironment">
<props>
<!-- подключает реализицию фабрики контекста (для сервера
WebLogic, это - обязательный параметр инициализации:-->
<prop key="java.naming.factory.initial">
weblogic.jndi.WLInitialContextFactory
</prop>
<!--параметры JNDI провайдера (для сервера WebLogic
это - дополнительный параметр и обычно не требуется:
<prop key="java.naming.provider.url">
t3://weblogic:7001
</prop> -->
</props>
</property>
</bean>
В общем случае, перечень параметров, необходимых для инициализации серверного контекста, (как и их конкретные значения), различны - для различных JEE серверов, и их настроек. Информацию об этом следует искать в технической документации сервера, предварительно ознакомившись с общими принципами использования контекста JEE сервера в главе 11. К сожалению, вероятно не все сервера, позволяют Spring настроить доступ извне, к их контексту , столь простым образом. В частности документация сервера GlassFish утверждает что в нем JEE-компонеты , вообще недоступны для объектов вне сервера, при поиске их через путь java:comp/env в JNDI, со всеми вытекающими отсюда последствиями.
Источники данных JNDI в Spring 2.0(и выше)
Если вы используете Spring 2.0, XML файл необходимый для извлечения данных из источника JNDI, значительно упрощается с использованием пространства имен jee. Вы можете использовать конфигурацию элементов из пространства имен jee, описав ваш элемент как <beans> следующим образом:
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="https://www.springframework.org/schema/jee"
xsi:schemaLocation="https://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans-2.0.xsd
https://www.springframework.org/schema/jee
https://www.springframework.org/schema/jee/spring-jee-2.0.xsd">
Пространство имен jee предлагает элемент <jee:jndi-lookup> для извлечения объектов из JNDI. Следующий XML файл эквивалентен точному указанию JndiObjectFactoryBean как было показано ранее:
<jee:jndi-lookup id="dataSource"
jndi-name="/jdbc/RantzDatasource"
resource-ref="true" />
jndi-name и resource-ref это атрибуты являющиеся ссылками прямо на jndiName и resourceRef свойства в JndiObjectFactoryBean.
Использование пулов соединений
Если вам не удается получить источник данных из JNDI, следующим наилучшим выходом является настройка пулов соединений с БД непосредственно в Spring. Хотя Spring не предоставляет собственной реализации пула , есть подходящий кандидат в проекте Jakarta Commons Database Connection Pools (DBCP)(https://jakarta.apache.org/commons/dbcp). Чтобы добавить DBCP к вашему приложению, надо либо скачать его и поместить JAR файл в classpath сборщика приложения Ant или добавить следующую <dependency> в Project Object Model (POM) сборщика приложений Maven 2:
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.1</version>
</dependency>
DBCP включает несколько источников данных, которые предоставляющих пулы соединений, но BasicDataSource часто используется потому, что он довольно прост в настройке в Spring и потому, что он напоминает Spring собственный DriverManagerDataSource (о котором мы будем говорить далее).
В нашем RoadRantz приложении, мы настроим bean для BasicDataSource следующим образом:
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName"
value="org.hsqldb.jdbcDriver" />
<property name="url"
value="jdbc:hsqldb:hsql://localhost/roadrantz/roadrantz" />
<property name="username" value="sa" />
<property name="password" value="" />
<property name="initialSize" value="5" />
<property name="maxActive" value="10" />
</bean>
Первые четыре свойства это начальные настройки BasicDataSource. Свойство driverClassName указывает на полное имя класса драйвера JDBC. Здесь мы настроили его для использования драйвера JDBC базы данных Hypersonic. C помощью cвойства url мы установили полный JDBC URL нашей базы данных. Наконец, свойства username и password используются для аутентификации при подключении к базе данных.
Эти четыре основных свойства определяют сведения о подключении для BasicDataSource. Кроме того, некоторые свойства могут быть использованы для настройки пула данных самого источника. В таблице 5.4 перечислены некоторые из наиболее полезных свойств конфигурации пула для BasicDataSource.
Для наших целей, мы настроили пул, с начальной емкостью в пять соединений. Если будет необходимо больше одновременных соединений, BasicDataSource будет добавлять их, вплоть до установленного максимума в 10 активных соединений.
Таблица 5.4 Свойства конфигурации пула BasicDataSource.
Свойство конфигурации | Назначение | |
---|---|---|
initialSize | Число соединений создаваемых при старте пула . | |
maxActive | Максимальное число одновременно допустимы активных соединений. Если 0 то не ограничено. | |
maxIdle | Максимально допустимое число простаивающих соединений которые не будут завершены . Если 0 то не ограничено. | |
maxOpenPreparedStatements | Максимальное количество скомпилированных запросов которые могут быть выделены из пула запросов одновременно. Если 0 то не ограничено. | |
maxWait | Время ожидания пулом возврата активного соединения в пул (при отсутствии свободных соединений) до выброса исключения. Если –1, ожидать бесконечно indefinitely. | |
minEvictableIdleTimeMillis | Сколько времени соединение может оставаться простаивающих в пуле прежде, чем оно может быть удалено из пула. | |
minIdle | Минимальное число простаивающих соединений, которые могут оставаться в пуле, без создания новых соединений | |
poolPreparedStatements | Разрешен или нет пул скомпилированных запросов (boolean). |
Иcпользование JDBC драйверов
Простейшие источники данных которыев Вы можете настроить в Spring, это те, что определены через драйвер JDBC. Spring предлагает на выбор два класса таких источников данных (оба в пакете org.springframework.jdbc.datasource):
- DriverManagerDataSource - возвращает новое соединение каждый раз, когда запрашивается соединение. В отличие от DBCP в BasicDataSource, соединения предоставляемые DriverManagerDataSource каждый раз создаются заново (что сопряжено с потерями производительности)
- SingleConnectionDataSource - возвращает одно и то же соединение каждый раз, когда соединение запрошивается. Хотя SingleConnectionDataSource и не является пулом, Вы можете думать о нем, как об источнике данных с пулом из одного соединения.
Настройка любого из этих источников данных, подобна тому, как мы настраивали в DBCP BasicDataSource:
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName"
value="org.hsqldb.jdbcDriver" />
<property name="url"
value="jdbc:hsqldb:hsql://localhost/roadrantz/roadrantz" />
<property name="username" value="sa" />
<property name="password" value="" />
</bean>
Разница лишь в том, что поскольку ни DriverManagerDataSource ни SingleConnectionDataSource не обеспечивают пулом соединений, в них нет свойств настроек конфигурации пула.
Хотя SingleConnectionDataSource и DriverManagerDataSource прекрасно подходят для небольших приложений и для процесса разработки приложений, Вы должны серьезно рассмотреть последствия использования любого из них в реальном production- приложении. Поскольку SingleConnectionDataSource имеет одно и только одно соединение для работы с базой данных, и это не очень хорошо работает в многопоточном приложении. В то же время, несмотря на то, что DriverManagerDataSource способен поддерживать несколько потоков, он каждый раз при запросе на соединение несет потери производительности на его создание . Из-за этих ограничений, я настоятельно рекомендую использовать пулы соединений в качестве источников данных.
Теперь, когда мы установили соединение с базой данных через источник данных, мы готовы для фактического доступа к базе данных. Самый элементарный способ получения доступа к базе данных , это использование JDBC. Итак, давайте начнем наше исследование абстракции доступа к данным в Spring, и посмотрим на то, как Spring упростит работу с обычным JDBC .
Использование JDBC совместно со Spring
Существует много технологий сохранения данных. Hibernate, iBATIS и JPA лишь некоторые из них. Несмотря на такое изобилие вариантов, брать и записывать Java-объекты прямо в базу данных это уже немного старомодный путь для заработка. Нет, стоп, а как же люди теперь деньги то делают ?! А, проверенным дедовским методом - сохраняя данные, старым... добрым... JDBC...
А почему собственно, нет ?! JDBC не требует знания языка запросов другого фреймворка. Он построен на вершине SQL, который является языком доступа к данным. Плюс, когда Вы используете JDBC, Вы можете куда более тонко настроить производительность доступа к данным, в сравнении с любой иной альтернативной технологией. И JDBC позволяет Вам воспользоваться всеми преимуществами фирменных особенностей Вашей базы данных, в то время когда другие провайдеры persistence могут этому препятствовать, или даже изо всех сил это запрещать.
Более того, JDBC позволяет работать с данными на гораздо более низком уровне, чем прочие persistence провайдеры , что позволяет например манипулировать отдельными столбцами в базе данных. Этот филигранный подход доступа к данным может пригодится в частности в таких приложениях как системы генерации отчетов, где нет никакого смысла преобразовывать непрерывные данные в объекты, чтобы затем немедленно извлекать эти данные обратно из объектов преобразуя их практически к исходному виду.
Но не все так безоблачно в мире JDBC. К его мощности, гибкости, и простоте, "в нагрузку" прилагаются куда менее изящные вещи...
Борьба с безудержным JDBC кодом
Хотя JDBC предоставляет API, который работает в тесном контакте с базой данных, Вы несете ответственность за управление всем, что касается доступа к базе данных. Это включает в себя управление ресурсами базы данных и обработку исключений.
Если Вы когда-либо писали программы для JDBC, которые вставляют данные в базу данных, листинг 5.1 не должн быть слишком чужд Вам
Листинг 5.1 Использование JDBC для вставки строки в базу данных
private static final String MOTORIST_INSERT =
"insert into motorist (id, email, password, " +
"firstName, lastName) " +
"values (null, ?,?,?,?)";
public void saveMotorist(Motorist motorist) {
Connection conn = null;
PreparedStatement stmt = null;
try {
conn = dataSource.getConnection();
//Opens connection
stmt = conn.prepareStatement(MOTORIST_INSERT);
//Creates statement
stmt.setString(1, motorist.getEmail());
//Binds
stmt.setString(2, motorist.getPassword());
//parameters
stmt.setString(3, motorist.getFirstName());
//Binds
stmt.setString(4, motorist.getLastName());
//parameters
//Executes statement
stmt.execute();
} catch (SQLException e) {
//Handles exceptions—
…
//somehow
} finally {
try {
if(stmt != null) { stmt.close(); }Cleans up
if(conn != null) { conn.close(); }resources
} catch (SQLException e) {}
}
}
О Святой Безудержный Код! Более 20 строк кода, чтобы вставить простой объект в базу данных. При этом, сами выполняемые операции JDBC, из разряда: "проще, просто не бывает". Так неужели надо обязательно написать так много строчек, чтобы сделать что-то столь простое? Строго говоря, это не совсем так. Лишь несколько строчек, на самом деле, делают вставку. Но JDBC требует, чтобы Вы корректно работали с соединениями и SQL-выражениям, и как-то еще обрабатывалось SQLException, которое может быть брошено.
Теперь давайте посмотрим листинг 5.2, где мы используем напрямую JDBC для обновления строки в таблице о автолюбителях в базе данных.
Листинг 5.2 Использование JDBC для обновления строки в базе данных
private static final String MOTORIST_UPDATE =
"update motorist " +
"set email=?, password=?, firstName=?, lastName=? " +
"where id=?";
public void updateMotorist(Motorist motorist) {
Connection conn = null;
PreparedStatement stmt = null;
//Opens connection
try {
conn = dataSource.getConnection();
//Creates statement
stmt = conn.prepareStatement(MOTORIST_UPDATE);
stmt.setString(1, motorist.getEmail());
//Binds parameters
stmt.setString(2, motorist.getPassword());
stmt.setString(3, motorist.getFirstName());
//Binds
stmt.setString(4, motorist.getLastName());
//parameters
stmt.setInt(5, motorist.getId());
//Executes statement
stmt.execute();
} catch (SQLException e) {
//Handles exceptions—
…
//somehow
} finally {
try {
if(stmt != null) { stmt.close(); }Cleans up
if(conn != null) { conn.close(); }resources
} catch (SQLException e) {}
}
}
На первый взгляд, листинг 5.2 может оказаться идентичным листингу 5.1. На самом деле, не считая SQL-строку и строку, в которой создается сам запрос к БД, они идентичны. Опять же, это довольно большое количество кода, чтобы сделать что-то столь простое как обновить одну строку в базе данных. Более того, здесь много повторяющегося кода. В идеале, мы бы должны были написать строчки, которые являются специфическими для конкретной задачи. В конце концов, те строчки, которые отличают листинга 5.2 от листинга 5.1. В остальном это просто шаблонный код.
Чтобы закончить наш обзор прямого использования JDBC, давайте посмотрим, как мы могли бы получить данные из базы данных. Как вы можете видеть в листинге 5.3, это тоже не слишком красиво.
Listing 5.3 Using JDBC to query a row from a database
private static final String MOTORIST_QUERY =
"select id, email, password, firstName, lastName " +
" from motorist where id=?";
public Motorist getMotoristById(Integer id) {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
//Opens connection
try {
conn = dataSource.getConnection();
//Creates statement
stmt = conn.prepareStatement(MOTORIST_QUERY);
stmt.setInt(1, id);
//Binds parameters
rs = stmt.executeQuery();
//Executes query
Motorist motorist = null;
if(rs.next()) {
motorist = new Motorist();
motorist.setId(rs.getInt("id"));
//Processes
motorist.setEmail(rs.getString("email"));
//results
motorist.setPassword(rs.getString("password"));
motorist.setFirstName(rs.getString("firstName"));
motorist.setLastName(rs.getString("lastName"));
}
return motorist;
} catch (SQLException e) {
//Handles exceptions—
…
//somehow
} finally {
try {
//??
}
if(rs != null) { rs.close(); }
if(stmt != null) { stmt.close(); }
if(conn != null) { conn.close(); }
} catch (SQLException e) {}
//Cleans up resources
}
return null;
Данный пример столь же многословен, как и предыдущие примеры вставки-обновления - а то возможно и больше. Это словно принцип Парето перевернутый с ног на голову: получается что на самом деле лишь 20 процентов кода необходимо, чтобы написать строки с запросами, в то время как оставшиеся 80 процентов - просто шаблонный код.
К настоящему времени Вы уже были должны увидеть, что многое из JDBC-кода, это стандартный код для создания соединений, SQL-запросов и обработки исключений. Начиная с этого места, мы закончим наконец эту пытку и не заставим Вас дальше смотреть, на этот ужасный, противный код.
Хотя в действительности, эта "простыня" шаблонного кода имеет важное значение. Очистка ресурсов и обработки ошибок делает доступ к данным надежнее. Без этого, ошибки остаются незамеченными и ресурсы могут быть оставлены открытыми, что приводит к непредсказуемому выполнению кода и утечке ресурсов. Так что мало того, что мы нуждаемся в этом коде, мы также должны убедиться, что весь он - корректный. Это еще одна причина, чтобы позволить фремворку иметь дел с этой "простыней", так как мы знаем, что это пишется один раз и уже написано за нас и правильно.
Работа с шаблонами JDBC
JDBC модуль в Spring освободит Ваш JDBC код, от необходимости управлению ресурсами и обработке исключений. Это даст Вам свободу писать только тот код, который необходим для перемещения данных в БД и обратно.
Как мы объясняли ранее, Spring скрывает весь вспомогательный код доступа к данным за классом шаблона доступа. Для JDBC, Spring поставляет три класса шаблонов, на выбор:
- JdbcTemplate - Самый основной шаблон JDBC в Spring, этот класс предоставляет простой доступ к базе данных через JDBC и простые индексно-параметризированные запросы.
- NamedParameterJdbcTemplate - Этот класс JDBC шаблона позволяет выполнять запросы, где значения параметров должны быть связаны с именоваными параметрами в SQL, вместо индексированных параметров.
- SimpleJdbcTemplate - Эта версия шаблона JDBC использует, такие новые возможности Java 5 как autoboxing, generics и varargs для упрощения работы с шаблоном.
Какой из этих шаблонов Вы выбираете, в значительной мере дело вкуса. Но в старых версииях JRE, SimpleJdbcTemplate не будет доступен, так как этот шаблон зависит от функций Java 5.
Чтобы помочь вам решить, какой из этих шаблонов JDBC будет лучше для Вас, давайте посмотрим на их один за другим, начиная с JdbcTemplate.
Использование JdbcTemplate
Все что JdbcTemplate должны делать, это выполнять свою работу реализуя интерфейс DataSource. Это делает достаточно легким в настройке бин JdbcTemplate в Spring, используя следующий XML фрагмент:
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
Фактически DataSource, на который ссылается свойство dataSource может быть любой реализацией javax.sql.DataSource, включая те, которые мы создавали в разделе посвященном источникам данных.
Теперь мы можем внедрить JdbcTemplate в наш DAO и использовать его для доступа к базе данных. Например, предположим, что RoadRantz DAO базируется на JdbcTemplate:
public class JdbcRantDao implements RantDao {
…
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
Тогда, мы должны связать свойство jdbcTemplate бина JdbcRantDao следующим образом:
<bean id="rantDao"
class="com.roadrantz.dao.jdbc.JdbcRantDao">
<property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>
С JdbcTemplate в распоряжении нашего DAO мы можем очень упростить метод saveMotorist () из Листинга 5.1. Новый метод saveMotorist() базирующийся на JdbcTemplate, приведен в Листинге 5.4.
Листинг 5.4 Основанный на JdbcTemplate метод сохранения данных saveMotorist()
private static final String MOTORIST_INSERT =
"insert into motorist (id, email, password, " +
"firstName, lastName) " +
"values (null, ?,?,?,?)";
public void saveMotorist(Motorist motorist) {
jdbcTemplate.update(MOTORIST_INSERT,Inserts
new Object[] { motorist.getEmail(),motorist.getPassword(),motorist
motorist.getFirstName(), motorist.getLastName() }); data
}
Мы думаем, Вы согласитесь с тем, что эта версия метода saveMotorist() является значительно проще. Тут больше нет кода для создания соединений и SQL-запросов, а также нет обработки исключений. Нет ничего кроме чистого кода вставки данных.
На самом деле там присутствует весь шаблонный код. Если вы его не видите, это не значит что его там нет. Он умно спрятан внутри JdbcTemplate. Когда вызывается метод update(), JdbcTemplate получает соединение, создает SQL-запрос и выполняет его.
Так же вы не видите как обрабатываются SQLException. Внутри себя JdbcTemplate "ловит" любые выброшенные исключения. Далее он преобразует обобщенное исключение SQLException в одно из более конкретных исключений доступа к данным из таблицы 5.1 и затем сгенерирует его выброс. Так как в Spring все исключения доступа к данным наследуются от RuntimeException, то мы не обязаны их ловить в методе saveMotorist().
Читать данные также проще используя JdbcTemplate. Листинг 5.5 показывает новую версию getMotoristById() в которой используются обратные вызовы JdbcTemplate чтобы отображать возвращаемый result set в объекты предметной области.
Листинг 5.5 Запрос на выборку к motorist используя JdbcTemplate
private static final String MOTORIST_SELECT =
"select id, email, password, firstName, lastName from motorist";
private static final String MOTORIST_BY_ID_SELECT =
MOTORIST_SELECT + " where id=?";
//Queries for
public Motorist getMotoristById(long id) {
//motorist
List matches = jdbcTemplate.query(MOTORIST_BY_ID_SELECT,
new Object[] { Long.valueOf(id) },
//Binds query
new RowMapper() {
//parameter
public Object mapRow(ResultSet rs, int rowNum)
throws SQLException, DataAccessException {
Motorist motorist = new Motorist();
motorist.setId(rs.getInt(1));
motorist.setEmail(rs.getString(2));
motorist.setPassword(rs.getString(3));
motorist.setFirstName(rs.getString(4));
motorist.setLastName(rs.getString(5));
return motorist;
}
});
//Maps query results to Motorist object
return matches.size() > 0 ? (Motorist) matches.get(0) : null;
}
This getMotoristById() method uses JdbcTemplate’s query() method to query a Motorist from the database. The query() method takes three parameters:
- Объект String, содержащий строку SQL-запроса, который будет использоваться, чтобы выбрать данные из базы данных
- Массив объектов Object, содержащий значения, которые будут связаны с индексируемыми параметрами запроса
- Объект класса RowMapper, извлекающий значения из ResultSet и создающий объекты предметной области (в этом случае Motorist)
Реальное волшебство происходит в объекте RowMapper. Для каждой строки, из возвращаемых запросом, JdbcTemplate будет вызывать mapRow() метод в RowMapper. Внутри RowMapper, мы написали код, который создает объект Motorist и инициализирует его значениями полученными из ResultSet.
getMotoristById() немного более длинен чем метод saveMotorist(). Даже если так, мы все равно сосредоточилось на том, чтобы восстанавливать объект Motorist из базы данных. В отличие от традиционного JDBC, нет никакого кода управления ресурсами или обработки исключений.
Использование именованных параметров
Метод saveMotorist() в Листинге 5.4 использовал индексированные параметры. Это означало то, что мы должны были обратить внимание на порядок следования параметров в запросе, и передать их значения в таком же порядке вызывая метод update() . Если нам когда-либо потребуется изменить SQL, таким образом, изменив порядок следования параметров в тексте запроса, мы также должны привести ему в соответствие список значений передаваемых в метод.
Произвольно, мы могли бы использовать только именованные параметры. Они позволяют нам дать каждому параметру в SQL явное имя и в последствии обратиться к параметру по этому имени, при связывании передаваемых в запрос значений. Для примера рассмотрим запрос MOTORIST_INSERT объявленный следующим образом:
private static final String MOTORIST_INSERT =
"insert into motorist (id, email, password, " +
"firstName, lastName) " +
"values (null, :email, :password, :firstName, :lastName)";
С именованными параметрами, порядок их следования для связывания значений не важен. Мы можем связать каждое значение по его имени. При изменении в SQL запросе порядка следования параметров, Java код их подстановки изменений не потребует.
К сожалению, шаблон JdbcTemplate не поддерживает именованные параметры. Вместо этого мы должны будем использовать специальный JDBC шаблон названный - NamedParameterJdbcTemplate. Настройка его конфигурации в Spring подобна настройке JdbcTemplate:
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
Поскольку NamedParameterJdbcTemplate - специальный шаблон JDBC и потому что он не является потомком JdbcTemplate, мы должны будем изменить свойство jdbcTemplate в нашем DAO, чтобы соответствовать новому классу шаблона:
public class JdbcRantDao implements RantDao {
…
private NamedParameterJdbcTemplate jdbcTemplate;
public void setJdbcTemplate(
NamedParameterJdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
Теперь мы готовы обновить наш метод saveMotorist() , чтобы использовать именованные параметры. Листинг 5.6 показывает новую версию saveMotorist(), использующую именованные параметры.
Листинг 5.6 Использование именованных параметров в Spring JDBC шаблонах.
public void saveMotorist(Motorist motorist) {
Map parameters = new HashMap();
parameters.put("email", motorist.getEmail());
parameters.put("password", motorist.getPassword());
parameters.put("firstName", motorist.getFirstName());
parameters.put("lastName", motorist.getLastName());
//Binds parameter values
jdbcTemplate.update(MOTORIST_INSERT, parameters);
//Performs
}
//insert
Первая вещь, которую Вы заметите, состоит в том, что эта версия saveMotorist() немного более длинна чем предыдущая. Это потому что именованные параметры связаны через java.util.Map. Несмотря на это, каждая строка фокусируется на главной цели - вставке объектов Motorist в базу данных. Снова отсутствует код управления ресурсами или обработки исключений, загромождающий главную цель метода.
Упрощение работы с JDBC в Java 5
Spring обеспечивает еще один специализированный шаблон JDBC, который Вы возможно захотите использовать. Если Вы бросите повторный взгляд на Листинг 5.4, то Вы увидите что параметры переданные в метод update() передавались как
массив объектов класса Object. Это - типичная стратегия
используемая, чтобы передать списки параметров с переменной длиной в метод. С Java 5 новые языковые конструкции (известных как varargs), позволяют передать списки параметров переменной длины, без необходимости создания массива Object.
Использование преимуществ Java 5 varargs означает что метод saveMotorist() может быть далее упрощен следующим образом:
public void saveMotorist(Motorist motorist) {
jdbcTemplate.update(MOTORIST_INSERT,
motorist.getEmail(), motorist.getPassword(),
motorist.getFirstName(), motorist.getLastName());
}
jdbcTemplate в этом новом методе saveMotorist() не является стандартным объектом JdbcTemplate. Вместо этого, используется специальный шаблон JDBC - SimpleJdbcTemplate, реализующий преимущества особенностей синтаксиса Java 5. Мы конфигурируем SimpleJdbcTemplate bean в Spring почти как обычный JdbcTemplate:
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.simple.SimpleJdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
Кроме того, мы должны будем изменить тип свойства jdbcTemplate на SimpleJdbcTemplate(вместо JdbcTemplate):
public class JdbcRantDao implements RantDao {
…
private SimpleJdbcTemplate jdbcTemplate;
public void setJdbcTemplate(SimpleJdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
Поскольку мощь SimpleJdbcTemplate проистекает из использования особенностей Java 5 , это все будет работать только под управлением соответствующего JRE (Java 5 или выше).
SimpleJdbcTemplate умеет больше чем просто оказывать varargs поддержку для, связывания параметров со значениями. Он также использует возможности Java 5 по поддержке autoboxing(автоупаковки типов) когда производится отображение возвращаемого запросом resultset в объект.
Повторно взгляните на метод getMotoristById() в Листинге 5.5. Две вещи Вы должны принять во внимание:
- параметр id предварительно должен был быть преобразован в класс-обертку(java.lang.Long), чтобы быть помещенным в массив Object.
- Типом, возвращаемым методом mapRow() объекта RowMapper, является java.lang.Object.
Это потому, что RowMapper предназначен, быть в достаточной мере абстрагированным и обобщенным, чтобы поддерживать любые типы объектов. Следствием является то, что возвращаемый результат, также должен быть самого общего типа из возможных: т.е. - Object. Теперь рассмотрим новую версию getMotoristById() в Листинге 5.7, которая использует для работы SimpleJdbcTemplate.
Листинг 5.7 Использование SimpleJdbcTemplate для извлечения объекта Motorist из базы данных.
public Motorist getMotoristById(long id) {
List<Motorist> matches = getSimpleJdbcTemplate().query(
MOTORIST_BY_ID_SELECT,
new ParameterizedRowMapper<Motorist>() {
//Returns Motorist
public Motorist mapRow(ResultSet rs, int rowNum)
throws SQLException {
Motorist motorist = new Motorist();
motorist.setId(rs.getInt(1));
motorist.setEmail(rs.getString(2));
motorist.setPassword(rs.getString(3));
motorist.setFirstName(rs.getString(4));
motorist.setLastName(rs.getString(5));
return motorist;
}
},
//Shows id isn’t wrapped
id
);
return matches.size() > 0 ? matches.get(0) : null;
}
Различия между Листингом 5.5 и Листингом 5.7 довольно тонкие. Первым различием отметьте то, что мы используем ParameterizedRowMapper, чтобы превратить результаты запроса в объект. ParameterizedRowMapper использует новшество Java5 возвращаемые ковариантные типы, для возможности устанавливать конкретный тип возвращаемый методом mapRow(). Другими словами, "новый RowMapper" знает, что он имеет дело с Motorist и не просто с Object.
Другое различие в том, что параметр id больше не нужно обязательно, оборачивать в объект Long , чтобы затем помещать в массив Object . Благодаря тому, что SimpleJdbcTemplate использует новые возможности Java 5 - autoboxing и varargs , теперь происходит автоматическое преобразования параметра id в класс-обертку.
Еще одно незначительное различие - то, что порядки следования параметров в методе query() несколько отличны для шаблонов JdbcTemplate и SimpleJdbcTemplate . Поскольку теперь метод query(), способен принимать varargs параметры, то varargs-набор значений (используемых для подстановки в SQL-запрос) пришлось поместить последним в списке параметров данного метода, во избежании неоднозначностей.
Иначе, методу query() было бы "непонятно" , когда-же список varargs заканчивается, а следующие после него прочие параметры метода - начинаются (это является общим синтаксическим ограничением использования varargs в Java5).
Как Вы уже увидели к настоящему времени, процесс сочинения собственного DAO-класса на JDBC-основе, включает конфигурирование bean-a подходящего JDBC шаблона , соединение его с Вашим классом DAO, ну и затем наконец, использование этого шаблона для доступ к базе данных. В целом, этот процесс подразумевает конфигурирование по крайней мере трех bean-ов в конфигурационном XML файле Spring: источника данных, класса шаблона, и собственно Вашего DAO. Давайте теперь посмотрим как же можно не включать в XML конфигурацию один из этих bean-ов если использовать классы поддержки DAO в Spring.
Классы поддержки DAO для JDBC.
Для всех основанных на JDBC DAO классов приложения, мы должны будем убедиться, что добавили свойство JdbcTemplate и его метод сеттера. И мы должны будем убедиться, что связали бин JdbcTemplate со свойством JdbcTemplate каждого DAO. Это не большое дело, если в приложении всего один DAO. Но если у Вас много объектов DAO, то потребуется много повторющегося кода.
Одним из решений проблемы было бы, создание общего родительскиого класса для всех Ваших DAO где и разместить JdbcTemplate. Тогда все Ваши DAO классы расширили бы этот класс и использовали бы JdbcTemplate родительского класса для доступа к данным. Рисунок 5.5 показывает предложенные отношения между DAO-приложением и базовым классом DAO.
Идея создания базового суперкласса DAO, который включал бы в себя JdbcTemplate - весьма хорошая идея, потому Spring уже содержит такой базовый класс "прямо из коробки". JdbcDaoSupport в Spring и есть суперкласс для того, чтобы наследовать от него, классы DAO для JDBC. Чтобы использовать JdbcDaoSupport просто напишите свой класс DAO, расшириряющий его. Например, в приложении RoadRantz класс DAO для JDBC мог бы быть написан таким образом:
public class JdbcRantDao extends JdbcDaoSupport
implements RantDao {
…
}
JdbcDaoSupport обеспечит удобный доступ к JdbcTemplate через метод getJdbcTemplate(). И тогда, наш saveMotorist() метод может быть переписан например вот так:
public void saveMotorist(Motorist motorist) {
getJdbcTemplate().update(MOTORIST_INSERT,
new Object[] { motorist.getEmail(), motorist.getPassword(),
motorist.getFirstName(), motorist.getLastName() });
}
Конфигурируя Ваш класс DAO в Spring, Вы могли бы непосредственно связать бин JdbcTemplate со свойством jdbcTemplate следующим образом:
<bean id="rantDao" class="com.roadrantz.dao.jdbc.JdbcRantDao">
<property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>
Это конечно будет работать, хотя и не особо отличается от того, как мы уже конфигурировали DAO, и здесь не расширен класс JdbcDaoSupport. Альтернативно, мы можем пропустить бин-посредник и связать хранилище данных непосредственно со свойством dataSource, которое JdbcRantDao наследует от JdbcDaoSupport:
<bean id="rantDao" class="com.roadrantz.dao.jdbc.JdbcRantDao">
<property name="dataSource" ref="dataSource" />
</bean>
Когда у JdbcRantDao конфигурируется свойство dataSource, это неявно создает экземпляр JdbcTemplate, избавляя Вас от необходимости явно объявлять бин JdbcTemplate в конфигурации Spring.
Поддержка DAO для именованных параметров
Ранее, мы уже показали Вам некоторые вариации на тему JdbcTemplate. Одна из них - NamedParameterJdbcTemplate, предлагает возможность использовать в запросах именованные параметры вместо индексируемых . Если Вы предпочитаете применять именованные параметры в запросах, Вы можете использовать класс NamedParameterJdbcDaoSupport как родительский для Вашего DAOs.
Например, если мы хотим использовать запросы с именованными параметрами в приложении RoadRantz, мы могли написать класс JdbcRantDao, наследующий NamedParameterJdbcDaoSupport следующим образом:
public class JdbcRantDao extends NamedParameterJdbcDaoSupport
implements RantDao {
…
}
Так же, как в случае с JdbcDaoSupport, класс NamedParameterJdbcDaoSupport обеспечивает удобный доступ к шаблону. Однако, вместо вызова getJdbcTemplate(), мы должны будем вызвать getNamedParameterJdbcTemplate(), чтобы получить требуемый нам шаблон JDBC. Вот так бы мог выглядеть метод saveMotorist() если использовать запросы с именованными параметрами:
public void saveMotorist(Motorist motorist) {
Map parameters = new HashMap();
parameters.put("email", motorist.getEmail());
parameters.put("password", motorist.getPassword());
parameters.put("firstName", motorist.getFirstName());
parameters.put("lastName", motorist.getLastName());
getNamedParameterJdbcTemplate().update(
MOTORIST_INSERT, parameters);
}
getNamedParameterJdbcTemplate() возвращает объект NamedParameterJdbcDaoSupport , что мы используем, чтобы выполнить обновление. Так же, когда мы использовали NamedParameterJdbcDaoSupport, значения параметров передаваемые в запрос помещаются в карту java.util.Map.
Упрощение поддержки DAO для JDBC в Java 5
Также, ранее, мы показывали Вам, как использовать JDBC шаблон работающий в стиле Java 5, и называемый - SimpleJdbcTemplate. Если Вы хотели бы использовать такие преимущества Java 5 как varargs и autoboxing в Ваших DAO, тогда Вы возможно решите сделать свой класс DAO наследуемым от SimpleJdbcDaoSupport:
public class JdbcRantDao extends SimpleJdbcDaoSupport
implements RantDao {
…
}
Получить доступ к SimpleJdbcTemplate, который содержится в внутри SimpleJdbcDaoSupport, можно просто вызвав его метод getSimpleJdbcTemplate(). Вот версия saveMotorist(), обновленная, чтобы использовать SimpleJdbcTemplate:
public void saveMotorist(Motorist motorist) {
getSimpleJdbcTemplate().update(MOTORIST_INSERT,
motorist.getEmail(), motorist.getPassword(),
motorist.getFirstName(), motorist.getLastName());
}
JDBC - это самый низко-уровневый способ получить доступ к данным в реляционной базе данных. JDBC шаблоны избавляют Вас от мучений написания кода управляющего ресурсами соединений и обрабатывающего исключения, давая Вам возможность, сосредоточиться на основной работе по извлечению, сохранению и обновлению данных.
Даже при том, что Spring снимает большую часть головной боли при работе с JDBC, с ростом размеров приложения этот путь все равно может стать неоправданно сложным. Чтобы разрешить проблемы доступа к большим объемам данных, Вы
возможно захотите изучить "настоящий" persistence фреймворк, такой как Hibernate.
Интеграция Hibernate и Spring.
Когда мы были детьми, езда на велосипеде была забавой, не так ли? Мы поехали бы в школу с утра. Когда уроки окончились, мы отправились бы в дом нашего лучшего друга. Когда же становилось поздно, пока наши родители вопили на нас за то, что мы шляемся дотемна, мы занимались всякой ерундой катаясь по темноте. Ну и дела, были забавой, в те дни ...
Когда мы выросли, нам потребовалось нечто побольше чем велосипед. Иногда у нас довольно приличное расстояние от дома до работы. Бакалея должна быть доставлена, а нашим детям нужно на тренировки по футболу. А если Вы еще и живете в Техасе, то без кондиционера - это уже не жизнь... Короче, наши потребности просто переросли наш велосипед.
JDBC - это велосипед в мире технологий хранения данных. Он чудесен для то чего придуман, и с некоторыми видами работы справляется просто великолепно. Но поскольку наши приложения становятся более сложными, давайте сформулируем наши требования к persistence. Итак, мы должны быть в состоянии отображать свойства объекта столбцы базы данных, неплохо было бы иметь наши SQL-выражения и запросы, созданными за нас, освобождая себя от печатания бесконечных строк из вопросительных знаков. Мы также нуждаемся в некоторых "фичах", которые являются более сложными:
- Lazy loading("ленивая загрузка")—Поскольку наши графы объектов становятся более сложными, мы иногда не хотим извлекать все дерево соотношений немедленно. Чтобы использовать типичный пример, предположим, что мы выбираем коллекцию объектов PurchaseOrder, и каждый из этих объектов в свою очередь содержит коллекцию объектов LineItem. Если мы интересуемся только признаками PurchaseOrder, не имеет никакого смысла захватывать данные из LineItem. Это весьма накладно. Lazy loading позволяет захватывать только те данные, которые действительно необходимы.
- Eager fetching("энергичная загрузка") - понятие противоположное lazy loading. Eager fetching позволяет Вам захватить весь граф объектов в рамках одного запроса. В случаях, где мы точно знаем, что мы нуждаемся и в объектах PurchaseOrder и в связанных с ними объектах LineItems, eager fetching позволяет нам получать их базы данных за одну операцию, избавляя от накладных повторных обращений к БД.
- Cascading("каскадирование") - Иногда изменения таблицы базы данных, также должны привести к изменениям в других связанных таблицах. Возвращаясь к нашему примеру заказа на поставку: когда объект заказа удален, мы также обычно хотим удалить связанные с ним объекты LineItems из базы данных.
Некоторые фреймворки способны предоставить подобную функциональность. Общее название для такой функциональности - object-relational mapping (ORM). Использование инструмента ORM для Вашего persistence-слоя может буквально спасти Вас, от тысяч строк лишнего кода и множества часов потраченных на его разработку. Это позволит Вам сместить фокус разработки с написания обильного и чреватого ошибками SQL-кода, на требования реализации самого приложения.
Spring оказывает поддержку для нескольких провайдеров ORM, включая Hibernateт, iBATIS, Apache OJB, Java Data Objects (JDO), Oracle TopLink, и Java Persistence API (JPA).
Как и в случае с поддержкой JDBC , Spring обеспечивает поддержку ORM-провайдеров предоставляя им точки интеграции для фреймворков, а также некоторые дополнительные сервисы:
- Интегрированную поддержку Spring для декларативных транзакций.
- Прозрачную обработку исключений.
- Потокобезопасные легковесные классы шаблонов
- Классы поддержки DAO
- Управление ресурсами
У нас просто нет достаточного количества места в этой главе, чтобы рассмотреть все ORM-провайдеры , которые поддержаны в Spring. Это даже хорошо, потому что поддержка в Spring различных вариантов реализации ORM довольно похожа. Как только Вы приобретаете навык использования
любого из ORM в Spring , Вы сможете легко переключиться на другой.
Давайте начнем изучение, глядя на то, как Spring интегрируется с тем, что, возможно является, самым популярным в использовании вариантом ORM — Hibernate . Позже в этой главе, мы также увидим , как Spring интегрируется с JPA и с iBATIS.
Hibernate это open source ORM-фреймворк, который получил существенную популярность в сообществе разработчиков. Он обеспечивает не только базовые возможности ORM, но также и все прочие "фичи" которые принято ожидать от полнофункционального инструмента ORM, а именно: lazy loading, еager fetching, кэширование и даже
распределенное кэширование.
Выбор версии Hibernate
На момент написания книги, последней доступной версией была - Hibernate 3.2. Но не так давно, последней доступной версией была Hibernate 2.x, и Вы возможно все еще сталкиваетесь с некоторыми приложениями, которые пока не используют Hibernate 3. Выбор между Hibernate 2 и Hibernate 3, является существенным потому что Hibernate API весьма различается между этими двумя версиями.
В то время как много функциональных усовершенствований и особенностей были введены в Hibernate 3, одно тонкое изменение усложнило его Spring интеграцию. Версия 2 Hibernate API имела структур пакета net.sf.hibernate , но в версии 3, его структура была изменена на org.hibernate. Перед командой разработчиков Spring это изменение поставило дилемму. Поскольку классы интеграции Spring-Hibernate, должны были импортировать классы или из net.sf.hibernate или из org.hibernate, пришлось выбирать между:
- а) Прекратить поддержку для Hibernate 2, и поддерживать в дальнейшем только Hibernate 3
- б) Разделить код Hibernate поддержки в Spring на две ветви — одну для Hibernate 2, другую для Hibernate 3
Признавая значение обратной совместимости, команда Spring решила выбрать второй вариант. Поддержка Hibernate 2 находится в дистрибутиве Spring в пакете org.springframework.orm.hibernate, а поддежка Hibernate 3 в пакете org.springframework.orm.hibernate3.
По большей части, классы в пакете для Hibernate 3 зеркально отражают соответствующие классы из Hibernate 2 пакета. Для Hibernate 3 в Spring также включена поддержка связывания полей БД через аннотации.
Всегда, когда это возможно старайтесь использовать Hibernate 3. Примеры в данной главе отражают этот выбор. Но если Ваши обстоятельство не предоставляют Вам, роскошь Hibernate 3, Вы обнаружите , что поддержка Hibernate 2 в Spring мало чем отличается от поддержки Hibernate 3, кроме имени пакета и невозможности использования в Hibernate 2 аннотаций при связывании полей БД.
Независимо от выбранной версии Hibernate , первый шаг которой Вам придется сделать - это сконфигурировать бин фабрики Hibernate-сессий в Spring.
Использование Hibernate шаблонов
Главный интерфейс для того, чтобы взаимодействовать с Hibernate - org.hibernate.Session. Интерфейс Session обеспечивает базовую функциональность доступа к данным, позволяя сохранять, обновлять, удалять, и загрузжать объекты в/из базы данных. Это через Hibernate интерфейс Session , объекты DAO в приложения будут удовлетворять все свои потребности в услугах persistence.
Стандартный способ получить ссылку на Hibernate объект Session - через реализацию SessionFactory интерфейса в Hibernate. Среди прочих вещей, SessionFactory ответственен за открытие, закрытие, и управление Hibernate Session.
Почти как JdbcTemplate-абстракция, прогнавшая от нас скуку работы с JDBC, Spring шаблон HibernateTemplate обеспечивает абстрактный слой над Hibernate Session. Главная ответственность HibernateTemplate состоит в том, чтобы упростить работу открытия, и закрытие Hibernate Session и преобразовывать собственные исключения Hibernate к одному из универсальных непроверяемых исключений Spring, перечисленных в таблице 5.1.
Следующий XML показывает, как конфигурировать HibernateTemplate в Spring:
<bean id="hibernateTemplate"
class="org.springframework.orm.hibernate3.HibernateTemplate">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
Свойству sessionFactory присваивается ссылка на используемую реализацию org.hibernate.SessionFactory. Здесь у Вас есть несколько вариантов, в зависимости от как Вы используете, Hibernate, чтобы отображать Ваши объекты в таблицы базы данных.
Использование классических маппинг-файлов Hibernate.
Если Вы будете использовать маппинг-файл классической XML-конфигурации Hibernate для связывания полей БД со свойствами объетов, то Вы захотите использовать Spring LocalSessionFactoryBean. Как показано в рисунке 5.6, LocalSessionFactoryBean это фабричный Spring бин , производящий локальный экземпляр Hibernate SessionFactory, который вытягивает свои метаданные о маппинге из одного или более XML маппинг-файлов.
- Возможно класс LocalSessionFactoryBean методологически правильнее было бы назвать LocalSessionFactoryFactoryBean. Но для избежания тавтологии, вспомогательная "Фабрика" была опущена.
Следующий фрагмент XML показывает, как конфигурировать LocalSessionFactoryBean, который загружает маппинг-файлы файлы для RoadRantz доменных объектов:
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mappingResources">
<list>
<value>com/roadrantz/domain/Rant.hbm.xml</value>
<value>com/roadrantz/domain/Motorist.hbm.xml</value>
<value>com/roadrantz/domain/Vehicle.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
</props>
</property>
</bean>
Свойство dataSource ссылается на любую реализацию интерфейса javax.sql.DataSource, включая уже нами рассмотренные. Свойство mappingResources принимает список одного или более путей к маппинг-файлам, как ресурсы в classpath. Здесь мы определили три маппинг-файла, которые описывают persistence из приложения RoadRantz. Наконец, свойство hibernateProperties позволяет нам обеспечить любую дополнительную конфигурацию, подходящую для Hibernate сессии. Как минимум, мы должны определить Hibernate диалект (то есть указать Hibernate , как нужно формировать SQL-запросы к конкретной используемой базе данных). Здесь мы оставили решение диалекта как placeholder переменную, которая будет заменена PropertyPlaceholderConfigurer (см. подробное описание в Главе 3).
Работа с аннотированными доменными объектами.
Ориентируясь на Java 5+, Вы можете выбрать использование аннотаций для связывания доменных объектов с persistence метаданными. Hibernate 3 поддерживает как JPA-аннотации, так и Hibernate-аннотации , для описания объектов подлежащих сохранению. При использовании аннотаций в Hibernate, AnnotationSessionFactoryBean в Spring работает почти так же, как LocalSessionFactoryBean, за исключением того, что он создает SessionFactory на основе аннотаций в одном или нескольких доменных классах (как показано на рисунке 5.7). Код XML, необходимый для конфигурирования AnnotationSessionFactoryBean Spring похож на XML для LocalSessionFactoryBean:
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="annotatedClasses">
<list>
<value>com.roadrantz.domain.Rant</value>
<value>com.roadrantz.domain.Motorist</value>
<value>com.roadrantz.domain.Vehicle</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
</props>
</property>
</bean>
Свойства dataSource и hibernateProperties служат одной цели как в случае с AnnotationSessionFactoryBean так и с LocalSessionFactoryBean. Однако, вместо настройки одного или нескольких маппинг-файлов мы должны будем настроить AnnotationSessionFactoryBean с одним или более классами, аннотированными для persistence в Hibernate. Здесь мы привели список доменных объектов в приложении RoadRantz.
Доступ к данным через Hibernate-шаблоны
Имея бин HibernateTemplate объявленым и связанным(wire) с фабрикой сессий, мы готовы начать использовать его для сохранения и извлечения объектов в базе данных. Листинг 5.8 показывает, часть HibernateRantDao, который вводится(inject) в HibernateTemplate.
Listing 5.8 Creating a HibernateTemplate-based DAO
public class HibernateRantDao implements RantDao {
public HibernateRantDao() {}
…
private HibernateTemplate hibernateTemplate;
public void setHibernateTemplate(HibernateTemplate template) {
this.hibernateTemplate = template;
} //Injects HibernateTemplate
}
HibernateRantDao принимает ссылку HibernateTemplate через setter injection("инъекцию сеттера") , поэтому нам нужно настроить его в Spring следующим образом:
<bean id="rantDao" class="com.roadrantz.dao.hibernate.HibernateRantDao">
<property name="hibernateTemplate" ref="hibernateTemplate" />
</bean>
Так как мы конкретизируем методы в HibernateRantDao, мы можем использовать введенный HibernateTemplate для доступа к объектам, хранящимся в базе данных. Например, вот saveVehicle() метод, используемый для сохранения объекта Vehicle в базу данных:
public void saveVehicle(Vehicle vehicle) {
hibernateTemplate.saveOrUpdate(vehicle);
}
Здесь мы используем метод saveOrUpdate() класса HibernateTemplate для сохранения Vehicle. SaveOrUpdate() проверяет объект, чтобы определить, является ли его поле ID - пустым(null). Если - null, то значит это - новый объект и, следовательно, он вставляется в базу данных. Если - не null, то предполагается, что это уже существующий объект и его данные обновляются.
Вот метод findVehiclesByPlate() , который использует метод find() класса HibernateTemplate для получения Vehicle путем запроса по названию штата и номерному знаку автомобиля:
public Vehicle findVehicleByPlate(String state,
String plateNumber) {
List results = hibernateTemplate.find("from " + VEHICLE +
" where state = ? and plateNumber = ?",
new Object[] {state, plateNumber});
return results.size() > 0 ? (Vehicle) results.get(0) : null;
}
А вот как Вы можете использовать метод load() в HibernateTemplate для загрузки конкретного экземпляра Motorist по значению его ID поля:
public Motorist getMotoristById(Integer id) {
return (Motorist) hibernateTemplate.load(Motorist.class, id);
}
Это примеры только трех из возможных способов, использования HibernateTemplate. Шаблон HibernateTemplate предлагает несколько десятков методов , которые помогут вам запрашивать и сохранять объекты через Hibernate. Если Вы уже знакомы с persistence методами предоставляемыми через интерфейс Session в Hibernate, Вы будете рады найти большинство из этих методов, доступными в HibernateTemplate.
В Листинге 5,8 мы явно вводим(inject) HibernateTemplate в HibernateRantDao. Это хорошо для ряда случаев, но Spring также предлагает и класс DAO-поддержки для Hibernate, который обеспечивает Вас шаблоном HibernateTemplate без его явного связывания. Давайте переделаем HibernateRantDao чтобы воспользоваться DAO-поддержкой Spring для Hibernate.
Создание DAO, поддерживаемых Hibernate.
Пока конфигурация HibernateRantDao включает в себя четыре бина. Источник данных привязан(wire) к бину фабрики сессий (LocalSessionFactoryBean или AnnotationSessionFactoryBean). Бин фабрики сессий привязан(wire) к HibernateTemplate. Наконец, HibernateTemplate привязан(wire) к HibernateRantDao, где он используется для доступа к базе данных.
Чтобы упростить все немного, Spring предлагает - HibernateDaoSupport. Это удобный класса поддержки DAO , который позволяет Вам привязать(wire) бин фабрики сессий непосредственно к классу DAO. Внутри себя, HibernateDaoSupport создает HibernateTemplate, который наш DAO можно использовать, как показано в UML на рисунке 5,8 .
Давайте переработаем HibernateRantDao для использования HibernateDaoSupport. Первый шаг изменений - сделать класс HibernateRantDao наследуемым от HibernateDaoSupport:
public class HibernateRantDao extends HibernateDaoSupport implements RantDao {
…
}
HibernateRantDao больше не нуждается в свойстве HibernateTemplate как это было в листинге 5.8. Вместо этого, Вы можете использовать метод getHibernateTemplate() , чтобы получить HibernateTemplate, который HibernateDaoSupport создает для Вас. Итак, следующий шаг заключается в изменении всех методов доступа к данным в HibernateRantDao, чтобы использовать getHibernateTemplate(). Например, вот метод saveMotorist() обновленый для варианта HibernateRantDao основанного на HibernateDaoSupport :
public void saveMotorist(Motorist motorist) {
getHibernateTemplate().saveOrUpdate(motorist);
}
Последнее, что осталось сделать, это перемонтировать HibernateRantDao в файле конфигурации Spring. С тех пор как HibernateRantDao больше не нуждается в ссылке на HibernateTemplate , мы должны будем удалить hibernateTemplate бин. Вместо этого HibernateTemplate, новый родитель нашего HibernateRantDao - HibernateDaoSupport, нуждается в Hibernate SessionFactory, чтобы он мог производить HibernateTemplate внутри себя. Таким образом, мы связываем(wire) sessionFactory бин со свойством sessionFactory в HibernateRantDao:
<bean id="rantDao" class="com.roadrantz.dao.hibernate.HibernateRantDao">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
На данный момент, мы создали DAO на Hibernate основе, для приложения RoadRantz, и связали его в конфигурацию Spring. Исключая транзакции (о которых мы поговорим в следующей главе), Вы теперь знаете почти все, что нужно знать об использовании Hibernate со Spring.
Но обратите внимание, что HibernateRantDao расширяет Spring-специфический класс. Это может быть и не проблемою, лично, для - Вас, но найдутся в мире люди, которые будут рассматривать это, как вторжение Spring, в код их приложения. По этой причине, давайте взглянем как воспользоваться поддержкой контекстных сессий в Hibernate 3, чтобы удалить Spring-специфические зависимости из кода Вашего DAO.
Использование контекстных сессий Hibernate 3
Одной из обязанностей HibernateTemplate является управление Hibernate объектами Session. Это включает в себя открытие и закрытие сессий, а также обеспечения одной сессии за одну транзакцию. Без HibernateTemplate, Вам бы ничего не оставалось, как загромождать ваши DAO, множеством шаблонным кодом управления сессиями.
Оборотная сторона HibernateTemplate в том, что он несколько навязчив. Когда мы используем HibernateTemplate в нашей DAO (напрямую или через HibernateDaoSupport), класс HibernateRantDao связан со Spring API. Хотя это может не вызывать особого беспокойство у одних разработчиков, другие могут найти вторжение Spring в свой код DAO нежелательным.
Однако, существует еще один вариант... Контекстные сессии, введенные в Hibernate 3, то это новый механизм, с помощью которого Hibernate сам управляет одним объектом Session за одну транзакцию. При этом нет необходимости в HibernateTemplate для обеспечения такого поведения. Таким образом, вместо связывания(wire) HibernateTemplate в ваши DAO, вы можете связать(wire) Hibernate SessionFactory вместо этого, как показано на рисунке 5.9.
Для иллюстрации рассмотрим эту новую "без-Spring" версию HibernateRantDao:
public class HibernateRantDao implements RantDao {
…
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
}
В этой новой HibernateRantDao, ссылка SessionFactory внедряется(inject) в свойство sessionFactory. После того, как SessionFactory происходит из Hibernate API, HibernateRantDao больше не зависит от Spring Framework. Вместо того чтобы использовать HibernateTemplate для выполнения persistence операций , вы сейчас запросите SessionFactory для текущей сессии.
Вот, например saveRant() метод обновленный для использования в контекстных сессиях Hibernate 3
public void saveRant(Rant rant) {
sessionFactory.getCurrentSession().saveOrUpdate(rant);
}
Когда дело доходит до конфигурирования HibernateRantDao в Spring, это ничем не отличается от того, как мы настроили его для HibernateDaoSupport-версии HibernateRantDao. Оба HibernateDaoSupport (в том числе наша новая "чисто-Hibernate" версия HibernateRantDao) требуют Hibernate SessionFactory для связывания(wire) его со своим свойством sessionFactory. В любом случае, SessionFactory бин (являющийся SessionFactory-производителем как LocalSessionFactoryBean или AnnotationSessionFactoryBean) подходит:
<bean id="rantDao" class="com.roadrantz.dao.hibernate.HibernateRantDao">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
Теперь у нас есть два варианта создания DAO на основе Hibernate, в нашем Spring приложении: HibernateTemplate или контекстная сессия. Какой мы выбираем? Следующие вещи, нужно учесть при принятии решения о выборе:
- Конечно, если вы используете Hibernate 2, то у вас нет другого выбора, кроме как использовать HibernateTemplate.
- Главным преимуществом контекстных сессий Hibernate является снижение связности(decouple) ваших DAO-реализаций и Spring.
- Основным недостатком контекстных сессий является то, что они бросают Hibernate-специфичные исключения. Хотя HibernateException и являются RuntimeException (что не обязвывает нас их обрабатывать и упрощает код) их иерархия специфична для Hibernate. Это может затруднить переход на другое решение ORM.
Несмотря на несколько попыток, придумать стандартный persistence framework, включая EJB-компоненты управления данными(EJB entity beans) и Java Data Objects (JDO), Hibernate занял положение persistence стандарта де-факто в сообществе Java. Даже с беспрецедентной популярностью, Hibernate, история может показатьто, что это в конечном счете лишь стадия для истинного стандарта persistence: Java Persistence API (JPA).
Хорошей новостью является то, что ORM абстракции Spring API, не ограничиваются Hibernate. Spring также обеспечивает API-абстракцию для JPA, похожим с Hibernate образом. Наш обзор интеграции Spring с persistence-провайдерами продолжается в следующем разделе с обсуждения того, как использовать в наших целях Java Persistence API
Интеграция Spring с Java Persistence API
From its beginning, the EJB specification has included the concept of entity
beans. In EJB, entity beans are a type of EJB that describes business objects that are
persisted in a relational database. Entity beans have undergone several tweaks
over the years, including bean-managed persistence (BMP) entity beans and container-
managed persistence (CMP) entity beans.
Entity beans both enjoyed the rise and suffered the fall of EJB’s popularity. In
recent years, developers have traded in their heavyweight EJBs for simpler POJObased
development. This presented a challenge to the Java Community Process to
shape the new EJB specification around POJOs. The result is JSR-220—also known
as EJB 3.
The portion of the EJB 3 specification that replaces entity beans is known as
the Java Persistence API (JPA). JPA is a POJO-based persistence mechanism that
draws ideas from both Hibernate and Java Data Objects (JDO) and mixes Java 5
annotations in for good measure.
With the Spring 2.0 release came the premiere of Spring integration with JPA.
The irony is that many blame (or credit) Spring with the demise of EJB. But now
that Spring provides support for JPA, many developers are recommending JPA for
persistence in Spring-based applications. In fact, some say that Spring-JPA is the
dream team for POJO development.
Spring’s JPA support mirrors the template-based support Spring provides for
the other persistence frameworks. Therefore, let’s get started with Spring and JPA
by looking at Spring’s JpaTemplate.
5.5.1 Using JPA templates
Keeping consistent with Spring’s support for other persistence solutions, the central
element of Spring-JPA integration is a template class. JpaTemplate, specifically,
is a template class that wraps a JPA EntityManager. The following XML
configures a JPA template in Spring:
<bean id="jpaTemplate"
class="org.springframework.orm.jpa.JpaTemplate">
<property name="entityManagerFactory"
ref="entityManagerFactory" />
</bean>
The entityManagerFactory property of JpaTemplate must be wired with an implementation of JPA’s javax.persistence.EntityManagerFactory interface, as shown in figure 5.10. JpaTemplate will use the EntityManagerFactory to produce EntityManagers as needed. I’ll show you where the entityManagerFactory bean comes from in section 5.5.2.
Similar to Spring’s other persistence templates, JpaTemplate exposes many of the same data access methods provided by a native JPA EntityManager. But unlike a plain JPA, JpaTemplate ensures that EntityManagers are opened and closed as necessary, involves the EntityManagers in transactions, and handles exceptions.
To write a JpaTemplate-based DAO, add a JpaTemplate property to the DAO and provide a setter for injection. Here’s an excerpt from JpaRantDao showing the JpaTemplate property:
public class JpaRantDao implements RantDao {
public JpaRantDao() {}
…
// injected
private JpaTemplate jpaTemplate;
public void setJpaTemplate(JpaTemplate jpaTemplate) {
this.jpaTemplate = jpaTemplate;
}
}
When configuring JpaRantDao in Spring, we simply wire the JpaTemplate into
the jpaTemplate property:
<bean id="rantDao"
class="com.roadrantz.dao.jpa.JpaRantDao">
<property name="jpaTemplate" ref="jpaTemplate" />
</bean>
With the JpaTemplate injected into the DAO, we’re now ready to use the template
to access persisted objects.
Accessing data through the JPA template
As we mentioned, JpaTemplate provides many of the same persistence methods that are provided by JPA’s EntityManager. This should make working with JpaTemplate second nature if you’re already familiar with JPA. For example, the following implementation of the saveMotorist() method uses JpaTemplate’s persist() method to save a Motorist object to the database:
public void saveMotorist(Motorist motorist) {
jpaTemplate.persist(motorist);
}
In addition to the standard set of methods provided by EntityManager, JpaTemplate also provides some convenience methods for data access. For example, consider the following getRantsForDay() method that uses a native JPA EntityManager to find all of the Rant objects that were entered on a given day:
public List<Rant> getRantsForDay(Date day) {
Query query = entityManager.createQuery(
"select r from Rant r where r.date=?1");
query.setParameter(1, day);
return query.getResultList();
}
The first thing getRantsForDay() has to do is create a Query object. Then it sets the query parameters. In this case, there’s only one query parameter, but you can imagine that a much more interesting example would involve one call to setParameter() for each parameter. Finally, the query is executed to retrieve the results. Contrast that method with the following implementation of getRantsForDay():
public List<Rant> getRantsForDay(Date day) {
return jpaTemplate.find(
"select r from Rant r where r.date=?1", day);
}
In this version, getRantsForDay() takes advantage of a convenient find() method offered by JpaTemplate. EntityManager doesn’t have such a simple find() method that takes a query and one or more parameters. Under the covers, JpaTemplate’s find() method creates and executes the Query for you, saving you a couple of lines of code.
The one unanswered question is where we get the entityManagerFactorybean that we wired into the JpaTemplate. Before we see what else Spring has to offer with regard to JPA integration, let’s configure the entityManagerFactory bean.
5.5.2 Configuring an entity manager factory
In a nutshell, JPA-based applications use an implementation of EntityManagerFactory to get an instance of an EntityManager. The JPA specification defines two kinds of entity managers:
- Application-managed—entity managers are created when an application directly requests an entity manager from an entity manager factory. With application-managed entity managers, the application is responsible for opening or closing entity managers and involving the entity manager in transactions. This type of entity manager is most appropriate for use in stand-alone applications that do not run within a Java EE container.
- Container-managed—entity managers are created and managed by a Java EE container. The application does not interact with the entity manager factory at all. Instead, entity managers are obtained directly through injection or from JNDI. The container is responsible for configuring the entity manager factories. This type of entity manager is most appropriate for use by a Java EE container that wants to maintain some control over JPA configuration beyond what is specified in persistence.xml. Both kinds of entity manager implement the same EntityManager interface. The key difference is not in the EntityManager itself, but rather in how the EntityManager is created and managed. Application-managed EntityManagers are created by an EntityManagerFactory obtained by calling the createEntityManagerFactory() method of the PersistenceProvider. Meanwhile, container-managed EntityManagerFactorys are obtained through PeristenceProvider’s createContainerEntityManagerFactory() method.
So what does this all mean for Spring developers wanting to use JPA? Actually,not much. Regardless of which variety of EntityManagerFactory you want to use, Spring will take responsibility for managing EntityManagers for you. If using an application-managed entity manager, Spring plays the role of an application and transparently deals with the EntityManager on your behalf. In the container-managed scenario, Spring plays the role of the container. Each flavor of entity manager factory is produced by a corresponding Spring
factory bean:
- LocalEntityManagerFactoryBean produces an application-managed EntityManagerFactory.
- LocalContainerEntityManagerFactoryBean produces a container-managed EntityManagerFactory.
It’s important to point out that the choice made between an application-managed EntityManagerFactory and a container-managed EntityManagerFactory is completely transparent to a Spring-based application. Spring’s JpaTemplate hides the intricate details of dealing with either form of EntityManagerFactory, leaving your data access code to focus on its true purpose: data access.
The only real difference between application-managed and container-managed entity manager factories, as far as Spring is concerned, is how each is configured within the Spring application context. Let’s start by looking at how to configure the application-managed LocalEntityManagerFactoryBean in Spring. Then we’ll see how to configure a container-managed LocalContainerEntityManagerFactoryBean.
Configuring application-managed JPA
Application-managed entity manager factories derive most of their configuration information from a configuration file called ersistence.xml. This file must appear in the META-INF directory within the classpath.
The purpose of the persistence.xml file is to define one or more persistence units. A persistence unit is a grouping of one or more persistent classes that correspond to a single data source. In simple terms, persistence.xml enumerates one or more persistent classes along with any additional configuration such as data sources and XML-based mapping files. Here’s a typical example of a persistence.xml file as it pertains to the RoadRantz application:
<persistence xmlns="https://java.sun.com/xml/ns/persistence"
version="1.0">
<persistence-unit name="rantzPU">
<class>com.roadrantz.domain.Motorist</class>
<class>com.roadrantz.domain.Rant</class>
<class>com.roadrantz.domain.Vehicle</class>
<properties>
<property name="toplink.jdbc.driver"
value="org.hsqldb.jdbcDriver" />
<property name="toplink.jdbc.url"
value="jdbc:hsqldb:hsql://localhost/roadrantz/roadrantz" />
<property name="toplink.jdbc.user"
value="sa" />
<property name="toplink.jdbc.password"
value="" />
</properties>
</persistence-unit>
</persistence>
Because so much configuration goes into a persistence.xml file, there’s very little configuration that’s required (or even possible) in Spring. The <bean> in listing 5.9 declares a LocalEntityManagerFactoryBean in Spring.
Listing 5.9 Configuring an application-managed EntityManagerFactory factory bean
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<!--Selects persistence unit -->
<property name="persistenceUnitName" value="rantzPU" />
</bean>
The value given to the persistenceUnitName property refers to the persistence unit name as it appears in persistence.xml.
The reason why much of what goes into creating an application-managed EntityManagerFactory is contained in persistence.xml has everything to do with what it means to be application managed. In the application-managed scenario (not involving Spring), an application is entirely responsible for obtaining an EntityManagerFactory through the JPA implementation’s PersistenceProvider. The application code would become incredibly bloated if it had to define the persistence unit every time it requested an EntityManagerFactory. By specifying it in persistence.xml, JPA can look in this well-known location for persistence unit definitions.
But with Spring’s support for JPA, the JpaTemplate will be the one that interacts with the PersistenceProvider—not our application code. Therefore, it seems a bit silly to extract configuration information into persistence.xml. In fact, it prevents us from configuring the EntityManagerFactory in Spring (so that, for example, we can provide a Spring-configured data source). For that reason, we should turn our attention to container-managed JPA.
Configuring container-managed JPA
Container-managed JPA takes a slightly different approach. When running within a container, an EntityManagerFactory can be produced using information provided by the container. This form of JPA is intended for use in JEE application servers (such as WebLogic or JBoss) where data source information will be configured through the application server’s configuration.
Nevertheless, container-managed JPA is also possible with Spring. Instead of configuring data source details in persistence.xml, you can configure this information in the Spring application context. For example, listing 5.10 shows how to configure container-managed JPA in Spring using LocalContainerEntityManagerFactoryBean.
Listing 5.10 Configuring a container-managed EntityManagerFactory ???
factory bean
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter">
<property name="showSql" value="true"/>
<property name="generateDdl" value="true"/>
<property name="database" value="HSQL"/>
</bean>
</property>
<property name="loadTimeWeaver">
<!--OpenConfigures data sources connection Configures JPA vendor-specifics -->
<bean class="org.springframework.instrument.classloading.SimpleLoadTimeWeaver" />
</property>
<!--Specifies load-time weaver-->
</bean>
Here we’ve configured the dataSource property with a Spring-configured data source. Any implementation of javax.sql.DataSource is appropriate, such as those that we configured in section 5.2. Although a data source may still be configured in persistence.xml, the data source specified through this property takes precedence.
The jpaVendorAdapter property can be used to provide specifics about the particular JPA implementation to use. In this case, we’re using TopLink Essentials, so we’ve configured it with a TopLinkJpaVendorAdapter. Several properties are set on the vendor adapter, but the most important one is the database property, where we’ve specified the Hypersonic database as the database we’ll be using. Other values supported for this property include those listed in table 5.5.
Certain dynamic persistence features require that the class of persistent objects be modified with instrumentation to support the feature. Objects whose properties are lazily loaded (that is, they will not be retrieved from the database until they are actually accessed) must have their class instrumented with code that knows to retrieve unloaded data upon access. Some frameworks use dynamic proxies to implement lazy loading. Others, such as JDO, perform class instrumentation at compile time.
JPA allows for load-time instrumentation of persistent classes so that a class is modified with dynamic persistence features as the class is loaded. The loadTimeWeaver property of LocalContainerEntityManagerFactoryBean lets us specify how the dynamic persistence features are woven into the persistent class. In this case, we’ve chosen Spring’s SimpleLoadTimeWeaver.
Which entity manager factory bean you choose will depend primarily on how you will use it. For simple applications, LocalEntityManagerFactoryBean may be
Table 5.5 The TopLink vendor adapter supports several databases. You can specify which database to use by setting its database property.
Database platform | Value for database property |
---|---|
IBM DB2 | DB2 |
Hypersonic | HSQL |
Informix | INFORMIX |
MySQL | MYSQL |
Oracle | ORACLE |
PostgresQL | POSTGRESQL |
Microsoft SQL Server | SQLSERVER |
Sybase | SYBASE |
sufficient. But because LocalContainerEntityManagerFactoryBean enables us to configure more of JPA in Spring, it is an attractive choice and likely the one that you’ll choose for production use.
5.5.3 Building a JPA-backed DAO
Previously, we wired a reference to an entity manager factory bean into a JpaTemplate and then wired the JpaTemplate into our DAO. But Spring’s JpaDaoSupport simplifies things a bit further by making it possible to wire the entity manager factory bean directly into our DAO class.
JpaDaoSupport provides the same convenience for JPA-backed DAOs as JdbcDaoSupport and HibernateDaoSupport provided for JDBC-backed and Hibernate-backed DAOs, respectively. As shown in figure 5.11, a JPA-backed DAO class extends JpaDaoSupport and is injected with an EntityManagerFactory (which may be produced by an EntityManagerFactoryBean). Under the covers, JpaDaoSupport creates a JpaTemplate and makes it available to the DAO for data access. To take advantage of Spring’s JPA DAO support, we will write JpaRantDao to subclass JpaDaoSupport:
public class JpaRantDao extends JpaDaoSupport implements RantDao {
…
}
Now, instead of wiring JpaRantDao with a JpaTemplate reference, we’ll wire it directly with the entityManagerFactory bean:
<bean id="rantDao" class="com.roadrantz.dao.jpa.JpaRantDao">
<property name="entityManagerFactory"
ref="entityManagerFactory" />
</bean>
Internally, JpaDaoSupport will use the entity manager factory wired into the entityManagerFactory property to create a JpaTemplate. As we flesh out the implementation of JpaRantDao, we can use the JpaTemplate by calling getJpaTemplate(). For example, the following reimplementation of saveMotorist() uses JpaDaoSupport’s getJpaTemplate() method to access the JpaTemplate and to persist a Motorist object:
public void saveMotorist(Motorist motorist) {
getJpaTemplate().persist(motorist);
}
Both Hibernate and JPA are great solutions for object-relational mapping. Through ORM, the gory details of data access—SQL statements, database connections, and result sets—are hidden and we can deal with data persistence at the object level. However, although ORM hides data access specifics, it also hinders (or even prevents) fine-grained control of how persistence is handled.
At the other end of the spectrum is JDBC. With JDBC, you have complete control over data access. But with this control comes complete responsibility for the tedium of connection management and mapping result sets to objects. Next up, let’s have a look at how Spring integrates with iBATIS, a persistence framework that strikes a balance between the absolute control of JDBC and the transparent mapping of ORM.
Интеграция Spring с iBATIS
Somewhere in between pure JDBC and ORM is where iBATIS resides. iBATIS is
often classified among ORM solutions such as Hibernate and JPA, but I prefer to
refer to it as an object-query mapping (OQM) solution. Although the iBATIS feature
set overlaps that of ORM in many ways, iBATIS puts you in full control of the
actual SQL being performed. iBATIS will still take responsibility for mapping
query results to domain objects, but you are free to author the queries in any manner
that suits you best.
Spring offers integration with iBATIS that mirrors that of its integration with JDBC and ORM frameworks. As with the other persistence frameworks described in this chapter, we’re going to keep our focus on how Spring integrates with iBATIS. If you’d like to learn more about iBATIS, I recommend you check out iBATIS in Action (Manning, 2007).
5.6.1 Configuring an iBATIS client template
At the center of the iBATIS API is the com.ibatis.sqlmap.client.SqlMapClient interface. SqlMapClient is roughly equivalent to Hibernate’s Session or JPA’s EntityManager. It is through this interface that all data access operations are performed.
Unfortunately, iBATIS shares many of the same problems as JDBC, Hibernate (pre-3.0), and JPA. Specifically, applications that use iBATIS for persistence are required to manage sessions. This session management code is typically nothing more than boilerplate code and distracts from the real goal of persisting objects to a database.
Furthermore, the persistence methods of SqlMapClient are written to throw java.sql.SQLException if there are any problems. As we’ve already discussed, SQLException is both a checked exception and too generic to react to in any useful way.
SqlMapClientTemplate is Spring’s answer to the iBATIS session management and exception-handling problems. Much like the other templates that we’ve covered in this chapter, SqlMapClientTemplate wraps an SqlMapClient to transparently open and close sessions. It also will catch any SQLExceptions that are thrown and rethrow them as one of Spring’s unchecked persistence exceptions in table 5.1.
Configuring an SqlMapClientTemplate
SqlMapClientTemplate can be configured in the Spring application context as
follows:
<bean id="sqlMapClientTemplate"
class="org.springframework.orm.ibatis.SqlMapClientTemplate">
<property name="sqlMapClient" ref="sqlMapClient" />
</bean>
The sqlMapClient property must be wired with a reference to an iBATIS SqlMap-
Client. In Spring, the best way to get an SqlMapClient is through SqlMapClient-
FactoryBean:
<bean id="sqlMapClient"
class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="sql-map-config.xml" />
</bean>
SqlMapClientFactoryBean is a Spring factory bean that produces an SqlMapClient. The dataSource property is wired with a reference to a javax.sql.DataSource. Any of the data sources described in section 5.2 will do.
Defining iBATIS SQL maps
As for the configLocation property, it should be configured with the path to an XML file that enumerates the locations of the iBATIS SQL maps. For the RoadRantz application, we’ve defined one SQL map file per domain object. Therefore, the sql-map-config.xml file will look like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMapConfig PUBLIC "-//iBATIS.com//
DTD SQL Map Config 2.0//EN"
"https://www.ibatis.com/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
<sqlMap resource="com/roadrantz/domain/rant-sql.xml" />
<sqlMap resource="com/roadrantz/domain/motorist-sql.xml" />
<sqlMap resource="com/roadrantz/domain/vehicle-sql.xml" />
</sqlMapConfig>
The three SQL map files are loaded as resources from the classpath under the same package as the domain objects themselves. As an example of iBATIS SQL mapping, listing 5.11 shows an excerpt from rant-sql.xml.
Listing 5.11 An example of mapping SQL queries to Rant objects
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
"https://www.ibatis.com/dtd/sql-map-config-2.dtd">
<sqlMap> namespace="Rant"
…
<resultMap id="rantResult"
class="com.roadrantz.domain.Rant">
<!--Defines result mpping-->
<result property="id" column="id" />
<result property="rantText" column="rant_text" />
<result property="postedDate" column="posted_date" />
<result property="vehicle" column="vehicle_id"
select="getVehicleById" />
</resultMap>
…
<select id="getRantsForDay"
resultMap="rantResult"
parameterClass="int">
<![CDATA[
Declares
select id, posted_date, rant_text, vehicle_id
getRantsForDay
from rant
query
where posted_date = #VALUE#Declares
]]>getRantsForDay
</select> query
…
</sqlMap>
In listing 5.11, we’ve defined a query that loads a list of Rant objects based on data
passed in as a parameter when the getRantsForDay query is performed. The
query is associated with a <resultMap> entry that tells iBATIS to convert each row
returned from the query into a Rant object. By the time our DAO sees the results,
they will be in the form of a List of Rant objects.
Using the template in a DAO
Before we can use the SqlMapClientTemplate to perform data access operations,
we must wire it into our DAO. The following excerpt from IBatisRantDao shows
an implementation of RantDao that is injected with an SqlMapClientTemplate:
public class IBatisRantDao implements RantDao {
…
// injected
private SqlMapClientTemplate sqlMapClientTemplate;
public void setSqlMapClientTemplate(
SqlMapClientTemplate sqlMapClientTemplate) {
this.sqlMapClientTemplate = sqlMapClientTemplate;
}
}
Since IBatisRantDao depends on an SqlMapClientTemplate, we’ll need to configure it as follows in the Spring configuration:
<bean id="rantDao"
class="com.roadrantz.dao.ibatis.IBatisRantDao">
<property name="sqlMapClientTemplate"
ref="sqlMapClientTemplate" />
</bean>
With the SqlMapClientTemplate injected into IBatisRantDao, we can now start fleshing out the persistence methods needed by the RoadRantz application. Here’s what the getRantsForDay() method looks like when written to use the injected SqlMapClientTemplate:
public List<Rant> getRantsForDay(Date day) {
return sqlMapClientTemplate.queryForList(
"getRantsForDay", day);
}
As with the other persistence mechanisms, Spring also provides DAO support for iBATIS. Before we end our exploration of Spring-iBATIS integration, let’s see how we can build the RoadRantz data access layer using iBATIS DAO support.
5.6.2 Building an iBATIS-backed DAO
The SqlMapClientDaoSupport class is a DAO support class for iBATIS. Much like the other DAO support classes offered by Spring, SqlMapClientDaoSupport is intended to be subclassed by a DAO implementation. As depicted in figure 5.12, SqlMapClientDaoSupport is a convenient superclass for iBATIS-backed DAOs that exposes an SqlMapClientTemplate object that can be used to execute
iBATIS queries. Rewriting the IBatisRantDAO class to use SqlMapClientDaoSupport, we have the following class definition.
public class IBatisRantDAO extends SqlMapClientDaoSupport
implements RantDao {
…
}
SqlMapClientDaoSupport provides an SqlMapClientTemplate for your DAO to use through its getSqlMapClientTemplate() method. As an example of how to use getSqlMapClientTemplate(), here’s the new getRantsForDay() method:
Figure 5.12 SqlMapClientDaoSupport is a convenient way to create iBATISbacked
DAO classes. SqlMapClientDaoSupport is injected with an
SqlMapClient that it wraps with an SqlMapClientTemplate to hide iBATIS
boilerplate code.
public List<Rant> getRantsForDay(Date day) {
return getSqlMapClientTemplate().queryForList(
"getRantsForDay", day);
}
The big difference between wiring an SqlMapClientTemplate directly into a DAO and subclassing SqlMapClientDaoSupport is that you can eliminate one of the beans in the Spring configuration. When a DAO subclasses SqlMapClientDaoSupport, you can bypass the SqlMapClientTemplate bean and wire an SqlMapClient (or an SqlMapClientFactoryBean that produces an SqlMapClient) directly into
the DAO:
<bean id="rantDao" class="com.roadrantz.dao.ibatis.IBatisRantDao">
<property name="sqlMapClient" ref="sqlMapClient" />
</bean>
As with the other persistence frameworks that integrate with Spring, the decision to either use a DAO support class or wire a template directly into your DAO is mostly a matter of taste. Although SqlMapClientDaoSupport does slightly simplify configuration of an iBATIS-backed DAO, you may prefer to inject an SqlMapClientTemplate into an application’s DAO—especially if your DAO class already subclasses
another base class.
Thus far, you’ve seen several ways of reading and writing data to a database, and we’ve built the persistence layer of the RoadRantz application. Now that you know how to read data from a database, let’s see how to avoid unnecessary database reads using Spring’s support for data caching.
5.7 Caching
In many applications, data is read more frequently than it is written. In the RoadRantz application, for instance, more people will visit the site to view the rants for a particular day or vehicle than those who post rants. Although the list of rants will grow over time, it will not grow as often as it is viewed.
Moreover, the data presented by the RoadRantz application is not considered time sensitive. If a user were to browse the site and see a slightly outdated list of rants, it probably would not have any negative impact on them. Eventually, they could return to the site to see a newer list of rants and no harm would be done.
Nevertheless, every time that a list of rants is requested, the DAO will go back to the database and ask for the latest data (which, more often than not, is the same data as the last time it asked).
Database operations are often the number-one performance bottleneck in an application. Even the simplest queries against highly optimized data stores can add up to performance problems in a high-use application.
When you consider the infrequency of data changes along with the performance costs of querying a database, it seems silly to always query the database for the latest data. Instead, it seems to make sense to cache frequently accessed (but not frequently updated) data.
On the surface, caching sounds quite simple: after retrieving some information, store it away in a local (and more easily accessible) location so that it’s handy the next time you need it. But implementing a caching solution by hand can be tricky. For example, have a look at HibernateRantDao’s getRantsForDay() method:
public List<Rant> getRantsForDay(Date day) {
return getHibernateTemplate().find("from " + RANT +
" where postedDate = ?", day);
}
The getRantsForDay() method is a perfect candidate for caching. There’s no way to go back in time and add a rant for a day in the past. Unless the day being queried for is today, the list of rants returned for any given day will never change. Therefore, there’s no point in always going back to the database for the list of rants that were posted last Tuesday. The database only needs to be queried once,
and then we can remember it in case we’re ever asked for it again. Now let’s modify getRantsForDay() to use some form of homegrown cache:
public List<Rant> getRantsForDay(Date day) {
List<Rant> cachedResult =
rantCache.lookup("getRantsForDay", day);
if(cachedResult != null) {
return cachedResult;
}
cachedResult = getHibernateTemplate().find("from " + RANT +
" where postedDate = ?", day);
rantCache.store("getRantsForDay", day, cachedResult);
return cachedResult
}
This version of getRantsForDay() is much more awkward. The real purpose of getRantsForDay() is to look up the rants for a given day. But the bulk of the method is dealing with caching. Furthermore, it doesn’t directly deal with some of the complexities of caching, such as cache expiration, flushing, or overflow.
Figure 5.13 The Spring Modules caching module intercepts calls to a bean’s methods, looking up data from a cache for quick data access and thus avoiding unnecessary slow queries to the database.
Fortunately, a more elegant caching solution is available for Spring applications. The Spring Modules project (https://springmodules.dev.java.net) provides caching via aspects. Rather than explicitly instrument methods to be cached, Spring
Modules caching aspects apply advice to bean methods to transparently cache their results.
As illustrated in figure 5.13, Spring Modules support for caching involves a proxy that intercepts calls to one or more methods of Spring-managed beans. When a proxied method is called, Spring Modules Cache first consults a cache to see whether the method has already been called previously with the same arguments. If so, it will return the value in the cache and the actual method will not be
invoked. Otherwise, the method is called and its return value is stored in the cache for the next time that the method is called.
In this section, we’re going to cache-enable the DAO layer of the RoadRantz application using Spring Modules Cache. This will make the application perform better and give our hard-working database a well-earned break.
5.7.1 Configuring a caching solution
Although Spring Modules provides a proxy for intercepting methods and storing
the results in a cache, it does not provide an actual caching solution. Instead, it
relies on a third-party cache solution. Several caching solutions are supported,
including:
- EHCache
- GigaSpaces
- JBoss Cache
- JCS
- OpenSymphony’s OSCache
- Tangosol’s Coherence
For our RoadRantz application, I’ve chosen EHCache. This decision was based primarily on my previous experience with EHCache and the fact that it is readily available in the Maven repository at www.ibiblio.org. However, regardless of which caching solution you choose, the configuration for Spring Modules Cache is quite similar for all caching solutions.
The first thing we’ll need to do is create a new Spring configuration file to declare caching in. While we could have worked the Spring Modules Cache configuration into any of the Spring context configuration files loaded in the RoadRantz application, it’s better to keep thing separate. So we’ll create roadrantz-cache.xml to hold our caching configuration.
As with any Spring context configuration file, roadrantz-cache.xml is rooted with the <beans> element. However, to take advantage of Spring Modules’ support for EHCache, we’ll need to declare the <beans> element to recognize the ehcache namespace:
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:ehcache="https://www.springmodules.org/schema/ehcache"
xsi:schemaLocation="https://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans-2.0.xsd
https://www.springmodules.org/schema/ehcache
https://www.springmodules.org/schema/cache/springmodules-ehcache.xsd">
…
</beans>
We’re using EHCache for the RoadRantz application, but if you’d like to use one of the other supported caching providers, you’ll need to swap out the namespace and schema declaration with the Spring Modules namespace and schema declaration appropriate for your choice. Table 5.6 lists each namespace along with its URI and schema URI.
Regardless of which caching provider you choose, you’ll be given several Spring configuration elements for configuring declarative caching in Spring. Table 5.7 catalogs these elements.
Table 5.6 The namespaces and schemas for the various caching providers supported by
Spring Modules.
Namespace Namespace URI Schema URI
ehcache https://www.springmodules.org/
schema/ehcache
https://www.springmodules.org/
schema/cache/springmodulesehcache.
xsd
gigaspaces https://www.springmodules.org/
schema/gigaspaces
https://www.springmodules.org/
schema/cache/springmodulesgigaspaces.
xsd /
jboss https://www.springmodules.org/
schema/jboss
https://www.springmodules.org/
schema/cache/springmodulesjboss.
xsd
jcs https://www.springmodules.org/
schema/jcs
https://www.springmodules.org/
schema/cache/springmodules-jcs.xsd
oscache https://www.springmodules.org/
schema/oscache
https://www.springmodules.org/
schema/cache/springmodulesoscache.
xsd
tangosol https://www.springmodules.org/
schema/tangosol
https://www.springmodules.org/
schema/cache/springmodulestangosol.
xsd
Table 5.7 Spring Modules’ configuration elements.
Configuration element What it’s for
<namespace:annotations> Declaring cached methods by tagging them with Java
5 annotations
<namespace:commons-attributes> Declaring cached methods by tagging them with
Jakarta Commons Attributes metadata
<namespace:config> Configuring the EHCache cache provider in Spring XML
<namespace:proxy> Declaring cached methods by declaring a proxy in
Spring XML
Since we’re using EHCache as the caching provider, we’ll need to tell Spring where to find the EHCache configuration file.4 That’s what the <ehcache:config>
element is for:
<ehcache:config
configLocation="classpath:ehcache.xml" />
Here we’re setting the configLocation attribute to tell Spring to load EHCache’s configuration from the root of the application’s classpath.
Configuring EHCache
As for the ehcache.xml file itself, we’ve configured it as shown in listing 5.12.
Listing 5.12 Configuring EHCache in ehcache.xml
<ehcache>
<defaultCache
maxElementsInMemory="500"
eternal="true"
overflowToDisk="false"
memoryStoreEvictionPolicy="LFU" />
<cache name="rantzCache"
maxElementsInMemory="500"
eternal="true"
overflowToDisk="false"
memoryStoreEvictionPolicy="LFU" />
</ehcache>
Configures
default cache
Configures
rantzCache
To summarize the code, we’ve configured two caches for EHCache to manage. The <defaultCache> element is mandatory and describes the cache that will be used if no other suitable cache is found. The <cache> element defines other caches and may appear zero or more times in ehcache.xml (once for each cache it defines). Here we’ve defined rantzCache as the only nondefault cache. The attributes specified on <defaultCache> and <cache> describe the behavior of the cache. Table 5.8 lists the attributes available when configuring a cache
in EHCache.
4
At the time of this writing, the EHCache configuration (and the specific configuration for the other
caching providers) is still specified in a provider-specific file external to Spring. But future versions may
expose provider-specific configuration through the <namespace:config> element so that the external
file is no longer necessary.
Table 5.8 Cache configuration attributes for EHCache.
Attribute Used to specify…
diskExpiryThreadIntervalSeconds How often (in seconds) the disk expiry thread is run—
that is, how often the disk-persisted cache is
cleansed of expired items. (Default: 120 seconds.)
diskPersistent Whether or not the disk store persists between
restarts of the VM. (Default: false.)
eternal Whether or not elements are eternal. If they are eternal,
the element never expires. (Required.)
maxElementsInMemory The maximum number of elements that will be cached
in memory. (Required.)
memoryStoreEvictionPolicy How eviction will be enforced when
maxElementsInMemory is reached. By default,
the least recently used (LRU) policy is applied. Other
options are first-in/first-out (FIFO) and less frequently
used (LFU). (Default: LRU.)
name The name of the cache. (Required for <cache>.)
overflowToDisk Whether or not the cache is allowed to overflow to
disk when the in-memory cache has reached the
maxElementsInMemory limit. (Required.)
timeToIdleSeconds The time (in seconds) between accesses before an
element expires. A value of 0 indicates that the element
can be idle forever. (Default: 0.)
timeToLiveSeconds The time (in seconds) that an element is allowed to
live in cache before it expires. A value of 0 indicates
that the element can live in cache forever without
expiring. (Default: 0.)
For the RoadRantz application, we’ve configured one default cache (because EHCache says that we have to) and another cache called rantzCache that will be the primary cache. We’ve configured both caches to allow for up to 500 elements to be kept in cache (with no expiration) and the least frequently used elements will be evicted. In addition, no disk overflow will be allowed.
With EHCache configured in the Spring application context, we are now ready to declare which beans and methods should have their results cached. Let’s start
by declaring a proxy that will cache the values returned from the methods of the
RoadRantz DAO layer.
5.7.2 Proxying beans for caching
We’ve already identified the getRantsForDay() method of HibernateRantDao as a candidate for caching. Back in the Spring context definition, we’ll use the <ehcache:proxy> element to wrap the HibernateRantDao with a proxy that will cache everything returned from getRantsForDay():
<ehcache:proxy id="rantDao"
refId="rantDaoTarget">
<ehcache:caching
methodName="getRantsForDay"
cacheName="rantzCache" />
</ehcache:proxy>
The <ehcache:caching> element declares which method(s) will be intercepted and which cache their return values will be cached in. For our purposes, method-Name has been set to intercept the getRantsForDay() method and to use the rantzCache cache.
You may declare as many <ehcache:caching> elements within <ehcache:proxy> as you need to describe caching for a bean’s methods. You
could use one <ehcache:caching> element for each cached method. Or you can also use wildcards to specify multiple methods with only one <ehcache:caching> element. The following <ehcache:caching> element, for example, will proxy all methods whose name starts with get to be cached:
<ehcache:caching
methodName="get*"
cacheName="rantzCache" />
Putting items into a cache is only half of the problem. After a while the cache will become littered with lots of data, some of which may no longer be relevant. Eventually, it may be desirable to clear out the cache (call it “Spring cleaning”) and start over. Let’s see how to flush the cache upon a method call.
Flushing the cache
Where the <ehcache:caching> element declares methods that populate the cache, <ehcache:flushing> declares methods that empty the cache. For example, let’s suppose that you’d like to clear out the rantzCache cache whenever the saveRant() method is called. The following <ehcache:flushing> element will handle that for you:
<ehcache:flushing
methodName="saveRant"
cacheName="rantzCache" />
By default, the cache specified in the cacheName attribute will be flushed after the method specified with methodName is invoked. But you can change the timing of the flush by using the when attribute:
<ehcache:flushing
methodName="saveRant"
cacheName="rantzCache"
when="before" />
By setting when to before we are asking for the cache to be flushed before the saveRant() method is invoked.
Declaring a proxied inner bean
Take note of <ehcache:proxy>’s id and refId attributes. The proxy produced by <ehcache:proxy> will be given an id of rantDao. However, that’s the id of the real HibernateRantDao bean. Therefore, we’ll need to rename the real bean to rantDaoTarget, which is referred to by the refId attribute. (This is consistent with how classic Spring AOP proxies and their targets are named. See section 4.2.3 for a reminder of how that works.)
If the id/refId arrangement seems awkward, then you also have the option of declaring the target bean as an inner bean of <ehcache:proxy>. For example, here’s <ehcache:proxy> reconfigured with HibernateRantDao as an inner bean:
<ehcache:proxy id="rantDao">
<bean class="com.roadrantz.dao.HibernateRantDao">
<property name="sessionFactory"
ref="sessionFactory" />
</bean>
<ehcache:caching
methodName="getRantsForDay"
cacheName="rantzCache" />
</ehcache:proxy>
Even using inner beans, you’ll still need to declare one <ehcache:proxy> element for each bean to be proxied and one or more <ehcache:caching> element for the methods. For simple applications, this may be okay. But as the number of cache-proxied beans and methods goes up, it will mean more and more XML in your Spring configuration.
If the inner-bean approach still seems clumsy or if you will be proxying several beans to be cached, you may want to consider using Spring Modules’ support for declarative caching by annotation. Let’s kiss <ehcache:proxy> goodbye and see how Spring Modules supports annotation-driven caching.
5.7.3 Annotation-driven caching
In addition to the XML-based caching configuration described in the previous section, Spring Modules supports declarative caching using code-level metadata. This support comes in two varieties:
- Java 5 annotations—This is the ideal solution if you’re targeting the Java 5 platform.
- Jakarta Commons Attributes—If you’re targeting pre–Java 5, you may choose Jakarta Commons Attributes.
For RoadRantz, we’re targeting Java 5. Therefore, we’ll be using Java 5 annotations to declare caching in the DAO layer. Spring Modules provides two annotations with regard to caching:
- @Cacheable—Declares that a method’s return value should be cached
- @CacheFlush—Declares a method to be a trigger for flushing a cache
Using the @Cacheable annotation, we can declare the getRantsForDay() method
to be cached like so:
@Cacheable(modelId="rantzCacheModel")
public List<Rant> getRantsForDay(Date day) {
return getHibernateTemplate().find("from " + RANT +
" where postedDate = ?", day);
}
The modelId attribute specifies a caching model that will be used to cache the values returned from getRantsForDay(). We’ll talk more about how the caching model is defined in a moment. But first, let’s use @CacheFlush to specify a flush action when the saveRant() method is called:
@CacheFlush(modelId="rantzFlushModel")
public void saveRant(Rant rant) {
getHibernateTemplate().saveOrUpdate(rant);
}
The modelId attribute refers to the flushing model that will be cleared when the saveRant() method is invoked. Speaking of caching and flushing models, you probably would like to know where those come from. The <ehcache:annotations> element is used to enable Spring Modules’ support for annotations. We’ll configure it in roadrantzcache.
xml as follows:
<ehcache:annotations>
<ehcache:caching id="rantzCacheModel"
cacheName="rantzCache" />
</ehcache:annotations>
Within the <ehcache:annotations> element, we must configure at least one <ehcache:caching> element. <ehcache:caching> defines a caching model. In simple terms, a caching model is little more than a reference to a named cache configured in ehcache.xml. Here we’ve associated the name rantzCacheModel with a cache named rantzCache. Consequently, any @Cacheable whose modelId is rantzCacheModel will target the cache named rantzCache.
A flushing model is quite similar to a caching model, except that it refers to the cache that will be flushed. We’ll configure a flushing model called rantzFlushModel alongside the rantzCacheModel using the <ehcache:flushing> element:
<ehcache:annotations>
<ehcache:caching id="rantzCacheModel"
cacheName="rantzCache" />
<ehcache:flushing id="rantzFlushModel"
cacheName="rantzCache" />
</ehcache:annotations>
The one thing that sets cache models apart from flushing models is that a flushing
model not only decides which cache to flush, but also when to flush it. By default,
the cache is flushed after @CacheFlush-annotated methods are called. But you can
change that by specifying a value for the when attribute of <ehcache:flushing>:
<ehcache:annotations>
<ehcache:caching id="rantzCacheModel"
cacheName="rantzCache" />
<ehcache:flushing id="rantzFlushModel"
cacheName="rantzCache"
when="before" />
</ehcache:annotations>
By setting the when attribute to before, the cache will be flushed before a @CacheFlush-annotated method is invoked.
5.8 Summary
Data is the lifeblood of an application. Some of the data-centric among us may even contend that data is the application. With such significance being placed on data, it’s important that we develop the data access portion of our applications in a way that is robust, simple, and clear, Spring’s support for JDBC and ORM frameworks takes the drudgery out of data access by handling common boilerplate code that exists in all persistence mechanisms, leaving you to focus on the specifics of data access as they pertain to your application.
One way that Spring simplifies data access is by managing the lifecycle of database connections and ORM framework sessions, ensuring that they are opened and closed as necessary. In this way, management of persistence mechanisms is virtually transparent to your application code.
Also, Spring is able to catch framework-specific exceptions (some of which are checked exceptions) and convert them to one of a hierarchy of unchecked exceptions that are consistent among all persistence frameworks supported by Spring. This includes converting nebulous SQLExceptions thrown by JDBC and iBATIS into meaningful exceptions that describe the actual problem that led to the exception being thrown.
We’ve also seen how an add-on module from the Spring Modules project can provide declarative caching support for your data access layer, increasing performance when often-requested, but scarcely updated, data is retrieved from a database.
Transaction management is another aspect of data access that Spring can make simple and transparent. In the next chapter, we’ll explore how to use Spring AOP for declarative transaction management.
------------------------
ТРИО теплый пол отзыв
Заработок на сокращении ссылок
Earnings on reducing links
Код PHP на HTML сайты
Категория: Книги по Java
Комментарии |