Шестая статья серии, посвященной технологии JavaEE. Это одна из самых важных и интересных статей. В ней расказано о технологии JPA и EJB. Приведено теорическое описание этих технологий. На практике, в статье описывается процесс создания класса-сущностей из таблиц базы данных и вместе с ними создание сессионых компонентов. При работе с переменными классов сущностей в jsp страницам немного рассказывается о технологии JSTL, благодаря которой количество встраиваемого кода (скриплетов и пр.) в html разметке минимальна.
Важно заметить, что если все уроки из предыдущих статей можно было реализовать на веб-сервере Apache Tomcat, то начиная с этой статьи он более не способен удолетворить нуждам JavaEE (об этом говорится во второй статье), поэтому важно, чтобы у Вас был сервер приложений, поддреживающий технологию EJB.
1Веб-сервер Apache Tomcat не походит для реализации технологии EJB и JPA в JavaEE приложениях!
1Для реализации примеров из данной статьи понадобится снимок 4 урока. Забрать здесь.
Сведения о EJB и JPA
Enterprise JavaBeans - это высокоуровневая, базирующаяся на использовании компонентов технология создания распределенных приложений, которая использует низкоуровневый API для управления транзакциями. EJB существенно упрощает разработку, поставку и настройку систему уровня предприятия, написанных на языке Java.
Технология Enterprise JavaBeans определяет некоторый набор универсальных и предназначенных для многократного использования компонентов, которые называются Enterprise beans. При создании распределенных систем её бизнес-логика реализована на уровне этих Компонентов.
Компоненты EJB выполняются под управлением Сервера EJB, который выполняет роль связующего звена между Контейнерами и операционной средой. Сервер EJB обеспечивает доступ Контейнерам EJB к системным сервиса, таким, как управление доступом к базам данных или мониторам транзакций, а также к другим приложениям.
EJB контейнер предоставляет следующие сервисы:
- Lifecycle Management: Индивидуальные ентерпрайс бины не нуждаются в явном управлении процессом, управлении потоками, активации объектов или их разрушении. EJB контейнер автоматически управляет жизненным циклом объекта.
- State Management: Индивидуальные ентерпрайс бины не нуждаются в явном сохранении или восстановлении состояния объекта между вызовами методов. EJB контейнер автоматически управляет состоянием объекта.
- Security: Индивидуальные бины не нуждаются в явной аутентификации пользователей или проверке авторизационных уровней. EJB контейнер автоматически производит все проверки безопасности.
- Transactions: Бин не обязан явно определять код транзакций для участия в распределенных транзакциях. EJB контейнер может автоматически управлять стартом, откатом, записью транзакций по требованию бина.
- Persistence: EJB контейнер автоматически управляет сохранением данных.
Session компоненты
Session компонент представляет собой объект, созданный для обслуживания запросов одного клиента. В ответ на удаленный запрос клиента, Контейнер создает экземпляр Компонента. Session-компонент всегда сопоставлен с одним клиентом; можно рассматривать его как представителя клиента на стороне EJB-сервера. Такие Компоненты могут "знать" о наличии транзакций - они могут отвечать за изменение информации в базах данных, но сами они непосредственно не связаны с представлением данных в БД.
Session-компоненты являются временными объектами и существуют сравнительно недолго. Обычно Session-компонент существует, пока создавший его клиент поддерживает с ним "сеанс связи". После завершения связи с клиентом компонент уже никак не сопоставлен с ним. Объект читается временным, так как в случае завершения работы (или "падения") сервера клиент должен будет создать новый Компонент.
Обычно Session-компонент содержит параметры, которые характеризуют состояние его взаимодействия с клиентом, т.е. он сохраняет некоторое состояние между вызовами удаленных методов в процессе сеанса связи с клиентом. Session-компоненты, которые поддерживают состояние, называются stateful-компонентами. Состояние существует, пока существует сеанс взаимодействия с клиентом.
Session-компонент может также не иметь состояния (stateless-компонент). Он не хранит характеристик своего взаимодействия с клиентом. Когда клиент вызывает один из его методов, разумеется, происходит изменение значений некоторых внутренних переменных, но эти значения имеют смысл только во время обработки этого единственного вызова - до его завершения. Таким образом, все экземпляры одного stateless-компонента являются идентичными (кроме интервалов времени, когда выполняется код одного из методов). Вследствие этого ничто не мешает одному и тому же экземпляру компонента обслуживать вызовы различных клиентов. Контейнер EJB может создать пул экземпляров таких Компонентов и выбирать любой из них для обслуживания клиентских запросов.
Entity-компоненты
Entity-компоненты представляют собой объектное представление данных из БД. Например, Entity-компонент может моделировать одну запись из таблицы реляционной базы данных. Несколько клиентов могут одновременно обращаться к одному экземпляру такого Компонента. Entity-компоненты изменяют состояние сопоставленных с ними баз данных в контексте транзакций.
Состояние Entity-компонентов в общем случае нужно сохранять, и "живут" они столько, сколько существуют в базе данных те данные, которые они представляют, а не столько, сколько существуют клиентский или серверный процессы. Остановка или "падение" Контейнера EJB не приводит к уничтожению содержащихся в нем Entity-компонентов.
За сохранность компонента может отвечать сам Компонент (Bean Managed Persistence, BMP) или его Контейнер (Container Managed Persistence, CMP). При использовании CMP все обязанности по сохранению состояния Компонента возлагаются на Контейнер. В случае BMP, Вы должны написать для Компонента нужный код, включая обращение к базам данных.
Каждый Entity-компонент характеризуется своим уникальным идентификатором - primary key. Обычно это тот же самый primary key, что и идентификатор данных в БД, например, совокупность ключевых полей записи в таблице.
Инфраструктура EJB
На рисунке ниже показаны различные элементы инфраструктуры EJB. Она должна обеспечивать канал связи с клиентом и другими Компонентами EJB. Хотя спецификация этого не требует, желательно, чтобы этот канал обеспечивал безопасность передаваемых данных, особенно при работе в Internet. Инфраструктура должна также обеспечивать соблюдение прав доступа к компонентам EJB.
JPA
JPA – это технология, обеспечивающая объектно-реляционное отображение простых JAVA объектов и предоставляющая API для сохранения, получения и управления такими объектами.
JPA – это спецификация (документ, утвержденный как стандарт, описывающий все аспекты технологии), часть EJB3 спецификации.
Сам JPA не умеет ни сохранять, ни управлять объектами, JPA только определяет правила игры: как что-то будет действовать. JPA также определяет интерфейсы, которые должны будут быть реализованы провайдерами. Плюс к этому JPA определяет правила о том, как должны описываться метаданные отображения и о том, как должны работать провайдеры. Дальше, каждый провайдер, реализуя JPA определяет получение, сохранение и управление объектами. У каждого провайдера реализация разная.
1
Преимущества использования JPA:
- Для выполнения статических и динамических запросов в JPA используется собственный язык запросов, схожий с SQL. При использовании языка запросов Java Persistence Query Language (JPQL) приложения можно переносить между базами данных различных поставщиков.
- Можно избежать написания низкоуровневого кода JDBC/SQL.
- JPA предоставляет прозрачные службы для кэширования данных и оптимизации производительности.
Если Вы впервые сталкиваетесь с этой технологией, то Вам может показаться все сложным и запутанным, но все станет намного понятнее после реализации примера на практике.
1Не отступать и не бояться!
Создание классов сущностей
А теперь будет только интересное и захватывающее. Начнем с созданием классов сущностей (Entity-classes). Средствами IDE NetBeans это делается очень легко.
- Откройте проект myblog.
- Нажмите на кнопку создания нового файла . В списке категорий выберите "Персистентность" (или "Сохраняемость", зависет от версии IDE).
- В сформировавшемся списке Типов Файлов выберите "Классы сущностей из базы данных".
- Нажмите "Далее >", откроется окно выбора базы данных. В выпадающем списке источников данных выберите созданный нами на предыдущем уроке jdbc ресурс jdbc/myblog. В левой колонке отобразятся доступные таблицы, нажмите "Добавить все >>", чтобы выбрать все таблицы.
- В следующем окне сопоставляются имена таблиц именам классов, заметьте что для таблицы groupuser_has_article это не делается. Далее увидите почему. Значение поля "Пакет" поменяйте на entity.
- Нажмите кнопку "Готово".
IDE создало 4 файла сущностей и один файл настроек persistence.xml.
Теперь на примере класса Articles (откройте его в окне редактора) разберемся как формируется entity-класс.
В самом верху мы видим следующий код:
@Entity
@Table(name = "articles")
@XmlRootElement
Аннотация @Entity указывает на то что этот класс есть сущность.
@Table(name="tableName") - указывается имя таблицы из БД с которой связан класс
@XmlRootElement - просто говорит о том что значения класса представляются как XML элементы в документе XML. Необязательный параметр.
Далее:
@NamedQueries({
@NamedQuery(name = "Articles.findAll", query = "SELECT a FROM Articles a"),
@NamedQuery(name = "Articles.findById", query = "SELECT a FROM Articles a WHERE a.id = :id"),
@NamedQuery(name = "Articles.findByTitle", query = "SELECT a FROM Articles a WHERE
a.title = :title"),
@NamedQuery(name = "Articles.findByDate", query = "SELECT a FROM Articles a WHERE a.date = :date")})
Если присмотреться, то вы заметите подобие SQL запросов в тексте, это не случайно. На самом деле это способ, который позволяет нам писать на EJB-QL аналога SQL для EJB. При написание названий полей мы обращаемся не к полям БД, а к переменным классам, поэтому важно, чтобы они совпадали по названию именно с переменными.
Идем ниже:
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "id")
private Integer id;
@Id - сообщает о том, что данное поле является идентификатором.
@GeneratedValue(strategy=GenerationType.IDENTITY) - сообщает, что значение поля будет генерироваться автоматически, а strategy=GenerationType.IDENTITY указывает на то, что этим вопросом будет заниматься сама БД.
@Basic - говорит о том, что поле будет сохраняться в БД, но мы то понимаем, что оно будет это делать в любом случае. Совсем не обязательное поле.
@Column(name="id") - думаю тут понятно, указывается название поля в БД с которым ассоциируется значение.
Ну и после всех аннотация приводится сама переменная.
Подобный образом описаны все поля БД. После них идет кое-что еще более интересное:
@JoinTable(name = "groupuser_has_articles", joinColumns = {
@JoinColumn(name = "articles_id", referencedColumnName = "id")}, inverseJoinColumns = {
@JoinColumn(name = "groupuser_name", referencedColumnName = "name")})
@ManyToMany
private Collection groupuserCollection;
Это ответ на вопрос "почему не создана сущность для таблицы grouppuser_has_articles". В аннотации мы описываем связь каждой из участвующих таблиц с таблицей grouppuser_has_articles.
@ManyToMany - указываем, что имеет место связь многим-ко-многим. И объявляем Коллекцию типа Groupuser. Теперь, имея переменную типа Articles, мы просто будем обращаться к значению переменой groupuserCollection чтобы получить список всех групп, которые привязаны к данной статье. Это делается прозрачно для нас, разработчиков, минуя таблицу grouppuser_has_articles. Удобно до жути.
@OneToMany(cascade = CascadeType.ALL, mappedBy = "articlesId")
private Collection messagesCollection;
Здесь уже понятнее, аннотацией @OneToMany мы объявляем связь один-ко-многим
Свойство CascadeType.ALL (а также INSTERT, UPDATE, DELETE, ALL включает в себя все три вида каскада). Это означает каскадное обновление записей в базе данных.
Далее идут конструкторы, гетеры и сетеры для значений переменных класса.
Теперь откройте файл persistance.xml. На вкладке "Конструктор" показаны свойства данной единицы персистентности. Запомним что ее название "myblogPU", далее пригодится. Также обозначен провайдер персистентности и источник данных. Из следующих свойств нам наиболее интересен "Режим общего кэша". Дело в том, что при работе с сущностями, EJB хранит их значение в кэше, и при очередном обращение к содержимому переменных, он не будет каждый раз обращаться к базе данных за новым значением, а просто будет брать его из кэша. Вполне очевидно потенциальная проблема, когда в базе данные изменились, а в кэше еще нет. О вариантах ее решения мы поговорим в следующих статьях.
Создание сессионных компонентов
Пришло время создать фасад сеанса.
Фасад сеанса — это шаблон проектирования. Как указано в документе Core J2EE Pattern Catalog, этот компонент пытается решить общие проблемы, возникающие в многопоточном приложении, например:
- Жесткие связи, приводящие к прямой зависимости между клиентскими и бизнес-объектами.
- Излишние вызовы методов между клиентом и сервером, приводящие к проблемам производительности сети.
- Недостаточная общность стратегий доступа клиентов, что вызывает недопустимое использование бизнес-объектов.
Фасад сеанса маскирует взаимодействие основных бизнес-объектов и создает уровень служб, предоставляющий только необходимые функциональные возможности. Это позволяет скрыть от клиента сложную схему взаимодействия участников. Таким образом, сеансовый компонент (т.е. фасад сеанса) управляет взаимодействием бизнес-объектов. Сеансовый компонент также управляет жизненным циклом участников, создавая, находя, редактируя и удаляя их в соответствии с рабочим процессом.
Приступим:
- Откройте меню создания нового файла, в столбце категорий выберите вновь "Персистентности", а в столбце типов файлов выберите "Сеансовые компоненты для сущностных классов".
- В окне выбора классов сущностей выберите все имеющиеся у нас entity-классы.
- В следующем окне смените имя пакета на session и нажмите "Готово".
Теперь в пакете session появилось 5 новых классов. Почему 5 а не 4? IDE создало AbstractFacade как абстрактный класс, заготовка для всех остальных. Откройте любой другой сессионный класс, Вы увидите что он наследуется от AbstractFacade.
Откроем один из классов фасада и посмотрим что внутри.
В самом начале идет аннотация
@Stateless
Как мы уже говорили ранее, это говорит о том что сессионый компонент без сохранения состояния.
@PersistenceContext(unitName = "myblogPU")
private EntityManager em;
Аннотация @PersistenceContext используется для добавления в класс интерфейса EntityManager, управляемого контейнером. Другими словами, контейнер EJB проекта GlassFish используется для открытия и закрытия интерфейсов EntityManager, когда это необходимо. Элемент unitName указывает блок сохранения состояния myblogPU, который был определен в файле persistence.xml
приложения.
1
EntityManager (диспетчер сущностей) — внутренний компонент интерфейса API сохранения состояния Java, отвечающий за сохранение состояния в базе данных. В книге EJB 3 в действии EntityManager описан следующим образом:
Интерфейс JPA EntityManager управляет сущностями в терминах реального обеспечения служб сохранения состояния. Когда сущности сообщают поставщику JPA о своем сопоставлении с базой данных, они не сохраняют свое состояние самостоятельно. Интерфейс EntityManager считывает метаданные ORM для сущности и производит операции сохранения состояния.
В нашем проекте имеется теперь модель состояния базы данных в виде класса сущностей и имеются сессионные компоненты (фасады) для доступа к классам сущностей.
Получение данных через EJB
Пришло время испытать наше творение и отобразить что-нибудь из базы данных на странице в браузере. Первое, что нам можно сделать, это отобразить список статей на главной странице сайта.
Сделаем предположение что список статей не будет меняться чаще чем ± 1 раз в сутки. Это значит, что было бы не плохо раз загрузить эти данные в память и не гоняться за ними каждый раз в базу данных при новом обращение к главной странице.
- Откройте наш web_controller.java (сервлет).
- Сразу после объявления класса объявим новый EJB компонент.
public class web_controller extends HttpServlet {
@EJB
ArticlesFacade articlesFacade;
Аннотация @EJB говорит о том что ниже объявлен EJB компонент, в нашем случае ArticlesFacade.
- Далее нам необходимо добавить функцию init(). Эта функция инициализация сервлета, выполняется только один раз, при старте сервлета. Можно написать ее ручками. В качестве ознакомления мы воспользуемся средствами IDE для генерации кода.
Нажмите правой кнопкой мыши по пустой строке после объявления EJB компонента, в появившемся контекстном меню выберите пункт "Вставка кода...".
- В следующем контекстном меню выберите пункт "Переопределение метода".
- Откроется список возможных переопределений методов. В нем раскройте список GenericServlet и выберите метод init().
- После этого IDE добавит следующий код:
@Override
public void init() throws ServletException {
super.init();
}
- Заменим содержимое вновь созданного метода на следующий:
@Override
public void init() throws ServletException {
getServletContext().setAttribute("articles", articlesFacade.findAll());
}
- Методом getServletContext() возращает интерфейс ServletContext. Этот интерфейс определяет набор методов, которые сервлет использует для связи с его контейнером сервлетов. Для любого одного web-приложения существует один и только один ServletContext, поэтому любую информацию в нем можно считать глобальную и доступную из любой точки приложения.
1Важно заметить что если приложение является распределенным и имеет соотвествующею пометку в дескриптере развертывания "distributed", то контекст создается по одному на каждую виртуальную машину. В этом случае его нельзя использовать как носитель глобальной информации. Для этого подойдет база данных.
Для ознакомления приведу список возможных контекстов:
Контекст | Описание контекста |
pageScope |
Контекст страницы (все переменые доступны только внутри страницы в которой были объявлены). |
requestScope |
Доступ к таким переменным имеют все страницы, сервлеты обслуживающие один, текущий, вот этот самый, запрос пользователя. |
sessionScope |
Доступ к переменным доступен отовсюду и сохраняются только для текущего сеанса пользователя, до тех пора пока сеанс не прекращен. |
applicationScope |
Доступ к переменным сохраняется изо всех страниц, размещенных внутри веб-приложения (самый глобальный контекст). |
param |
В этом контексте находятся все переменные, полученные страницей от пользователя GET или POST запросом. |
paramValues |
Список значений тех переменных, которые были получены от пользователя GET или POST запросом, правда, формат отличен от предыдущего случая. Если там param фактически имел тип HashMap<String, String>, то здесь HashMap<String, String [] >. |
header |
В этом объекте хранится информация об http-заголовках, которые были переданы от браузера клиента вашему веб-серверу. |
headerValues |
Список значений http-заголовков. |
initParam |
Конфигурационные параметры, указанные в файле web.xml |
cookie |
Список переменных, помещенных внутрь cookie. |
Вернемся к нашему контексту, в него методом setAttribute мы поместили список всех наших статей. В первом параметры мы указали имя атрибута "articles", во втором получили сами значения, используя метод findAll() у объявленного нами ранее фасада класса Articles. Данный метод вернул нам объект типа List.
После того, как мы получили список статей и погрузили их в глобальный контекст, необходимо получить их в jsp странице и вывести пользователю.
- Откройте в редакторе файл header.jspf и в самом верху страницы добавьте следующие строки:
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
- Этой строкой мы объявили библиотеки тегов.
JSTL - Java Standard Tag Library или другими словами - стандартная библиотека тегов. Она представляет из себя библиотеку тегов, которые инкапсулируют базовый функционал, необходимый для написания динамических JSP страниц. JSTL была создана для того, чтобы позволить JSP программистам писать с использованием тегов, вместо того, чтобы встраивать java код в скриплеты.
Объявленный первым тэг является так называемым Сore Tag Library, и содержит в себе методы циклов, выполнений условий, базовый ввод-вывод. Также имеется другие библиотеки, о них поговорим в следующей статье. Второй тэг объявляет библиотеку которая содержит всевозможные функции для работы с переменными.
1Если по какой-то причине веб-сервер не понимает jstl тэги, необходимо скачать библиотеку тегов с сайта glassfish и поместить в ее в директорию lib в папке сервера. После этого перезапустить сервер.
- Откройте в окне редактора файл index.jsp.
- Удалите все содержимое тегов и внуть тега вставьте следующий код:
<section>
<c:forEach var="article" items="${articles}">
<article>
<h1>${article.title}</h1>
<div class="text-article">
${fn:substring(article.text,0,300)} ...
</div>
<div class="fotter-article">
<span class="read"><a href="article?id=${article.id}">
Читать...</a></span>
<span class="date-article">Дата статьи: ${article.date}</span>
</div>
</article>
</c:forEach>
</section>
- Тэгом запускается цикл перебора содержимого аттрибута articles, который мы создали в сервлете. Содержимое атрибута на каждой итерации присваивается значению article. Далее работаем с этим значением, которое является содержимым класса Articles. Когда мы обращаемся к полю title или любому другому, то на самом деле идет обращение к гетеру метода (например getTitlte()).
Если сейчас развернуть приложение, то результатов на главной странице мы не увидим. Все потому, что сейчас сервлет не загружен т.к. к нему не было ни одного обращения. Чтобы сервлет грузился сразу при старте приложения, добавьте следующий код в аннотацию web_controller'а.
@WebServlet(name = "web_controller", loadOnStartup=1,
urlPatterns = {"/article", "/registration"})
Теперь мы увидим результат нашей работы.
Все хорошо, за исключением того, что дата выводится не в самом удобном виде. Как мы уже знаем, все обращения к значениям из класса сущности осуществляются не явно через гетеры. Откроем в редакторе файл Articles.java.
- Найдите функцию
public Date getDate() {
return date;
}
- Замените эту функцию на следующию:
public String getDate() {
try{
return new SimpleDateFormat("dd.MM.yyyy").format(this.date);
}catch (NullPointerException e){
return "Дата не определена";
}
}
- Обновите страницу. Теперь дата отображается в привычном виде.
1 Снимок готового урока можно забрать на GitHub.
Это был один из самых важных уроков. Во время его прохождения наверняка возникнут ошибки и вопросы, однако, если Вы сможете их решить и разобраться во всех вопросах, то дальнейшее изучение технологии JavaEE дастся намного легче. Вопросы также можно оставлять в комментариях, автор с удовольствием на них ответит.
В следующем уроке будет подробно рассмотрена технология JSTL, показан метод получения информации от пользователя и ее обработка.
Комментарии
Chet na onedeveloper id=7 trabl s kodirovkoy, nu da ladno. Mojno privesti primer poluchenija em treh svyaznih tablic s posleduyuschim vivodom. Vivod v principe ponyaten v xhtml sozdaem ukazatel na metod tipa value="#{class.method}" a vot cac poluchit' em soderjashiy polya raznih tablic i vivesti ih?
Entety-компоненты
опечатка в статье
Спасибо, поправил.