JAVA EE:Разработка web-приложения. JPA. EJB. JSTL.
Поиск по сайту:
О разработчеке сайта
Header image

Новые статьи и уроки JAVA

JAVA EE:Разработка web-приложения. JPA. EJB. JSTL.

Шестая статья серии, посвященной технологии 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.

Компоненты, Контейнеры и Сервера 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 это делается очень легко.

  1. Откройте проект myblog.
  2. Нажмите на кнопку создания нового файла Кнопка создания нового файла. В списке категорий выберите "Персистентность" (или "Сохраняемость", зависет от версии IDE).
  3. В сформировавшемся списке Типов Файлов выберите "Классы сущностей из базы данных".

    Окно выбора создаваемого файла
  4. Нажмите "Далее >", откроется окно выбора базы данных. В выпадающем списке источников данных выберите созданный нами на предыдущем уроке jdbc ресурс jdbc/myblog. В левой колонке отобразятся доступные таблицы, нажмите "Добавить все >>", чтобы выбрать все таблицы.

  5. В следующем окне сопоставляются имена таблиц именам классов, заметьте что для таблицы groupuser_has_article это не делается. Далее увидите почему. Значение поля "Пакет" поменяйте на entity.

    Создание классов сущностей из базы данных
  6. Нажмите кнопку "Готово".

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, этот компонент пытается решить общие проблемы, возникающие в многопоточном приложении, например:

  • Жесткие связи, приводящие к прямой зависимости между клиентскими и бизнес-объектами.
  • Излишние вызовы методов между клиентом и сервером, приводящие к проблемам производительности сети.
  • Недостаточная общность стратегий доступа клиентов, что вызывает недопустимое использование бизнес-объектов.

Фасад сеанса маскирует взаимодействие основных бизнес-объектов и создает уровень служб, предоставляющий только необходимые функциональные возможности. Это позволяет скрыть от клиента сложную схему взаимодействия участников. Таким образом, сеансовый компонент (т.е. фасад сеанса) управляет взаимодействием бизнес-объектов. Сеансовый компонент также управляет жизненным циклом участников, создавая, находя, редактируя и удаляя их в соответствии с рабочим процессом.

Приступим:

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

    Создание сессионого компонента
  2. В окне выбора классов сущностей выберите все имеющиеся у нас entity-классы.

    Создание сессионого компонента
  3. В следующем окне смените имя пакета на 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 раз в сутки. Это значит, что было бы не плохо раз загрузить эти данные в память и не гоняться за ними каждый раз в базу данных при новом обращение к главной странице.

  1. Откройте наш web_controller.java (сервлет).
  2. Сразу после объявления класса объявим новый EJB компонент.
    public class web_controller extends HttpServlet {

        @EJB
        ArticlesFacade articlesFacade;
    Аннотация @EJB говорит о том что ниже объявлен EJB компонент, в нашем случае ArticlesFacade.
  3. Далее нам необходимо добавить функцию init(). Эта функция инициализация сервлета, выполняется только один раз, при старте сервлета. Можно написать ее ручками. В качестве ознакомления мы воспользуемся средствами IDE для генерации кода.
    Нажмите правой кнопкой мыши по пустой строке после объявления EJB компонента, в появившемся контекстном меню выберите пункт "Вставка кода...".

    Вызов контекстного меню
  4. В следующем контекстном меню выберите пункт "Переопределение метода".

    Вызов контекстного меню
  5. Откроется список возможных переопределений методов. В нем раскройте список GenericServlet и выберите метод init().

    Переопределение мемода
  6. После этого IDE добавит следующий код:
    @Override
        public void init() throws ServletException {
            super.init();
        }
  7. Заменим содержимое вновь созданного метода на следующий:
    @Override
        public void init() throws ServletException {
            getServletContext().setAttribute("articles", articlesFacade.findAll());
        }
  8. Методом 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 странице и вывести пользователю.

  1. Откройте в редакторе файл header.jspf и в самом верху страницы добавьте следующие строки:
    <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
  2. Этой строкой мы объявили библиотеки тегов.
    JSTL - Java Standard Tag Library или другими словами - стандартная библиотека тегов. Она представляет из себя библиотеку тегов, которые инкапсулируют базовый функционал, необходимый для написания динамических JSP страниц. JSTL была создана для того, чтобы позволить JSP программистам писать с использованием тегов, вместо того, чтобы встраивать java код  в скриплеты.  
    Объявленный первым тэг является так называемым Сore Tag Library, и содержит в себе методы циклов, выполнений условий, базовый ввод-вывод. Также имеется другие библиотеки, о них поговорим в следующей статье. Второй тэг объявляет библиотеку которая содержит всевозможные функции для работы с переменными.

    1Если по какой-то причине веб-сервер не понимает jstl тэги, необходимо скачать библиотеку тегов с сайта glassfish и поместить в ее в директорию lib в папке сервера. После этого перезапустить сервер.

  3. Откройте в окне редактора файл index.jsp.
  4. Удалите все содержимое тегов и внуть тега вставьте следующий код:
    <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>
  5. Тэгом запускается цикл перебора содержимого аттрибута articles, который мы создали в сервлете. Содержимое атрибута на каждой итерации присваивается значению article. Далее работаем с этим значением, которое является содержимым класса Articles. Когда мы обращаемся к полю title или любому другому, то на самом деле идет обращение к гетеру метода (например getTitlte()).

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

@WebServlet(name = "web_controller", loadOnStartup=1, 
urlPatterns = {"/article", "/registration"})

Теперь мы увидим результат нашей работы.

Главная страница сайта

Все хорошо, за исключением того, что дата выводится не в самом удобном виде. Как мы уже знаем, все обращения к значениям из класса сущности осуществляются не явно через гетеры. Откроем в редакторе файл Articles.java.

  1. Найдите функцию
    public Date getDate() {
            return date;
        }
  2. Замените эту функцию на следующию:
    public String getDate() {
        try{
        return new SimpleDateFormat("dd.MM.yyyy").format(this.date);
        }catch (NullPointerException e){
          return "Дата не определена";
        }
        }
  3. Обновите страницу. Теперь дата отображается в привычном виде.

 

1 Снимок готового урока можно забрать на GitHub.

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

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

27.08.2012

Комментарии

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?

18.11.2014 Автор: D

Entety-компоненты

опечатка в статье

21.01.2015 Автор: andrey

Спасибо, поправил.

12.03.2015 Автор: Анатолий