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

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

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

Восьмая статья серии посвященной технологии JavaEE. В этой статье мы продолжем изучать возможности EJB, реализовав функционал страницы регистрации пользователей. Для этого нам понадобится формирование собственного запроса к БД с возможностью добавления параметром, добавление новых записей в базу, а  так же использование транзакций для обеспечения отказоустойчивости операций. Всю отвественность за Сохраняемость компонентов мы возможем на контейнер в котором лежит данный компонент, то есть будем использовать "Container Managed Persistence".

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

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

Зделать этом можно выполнив следующею команду в mysql shell'e:

CREATE  TABLE `myblog`.`Contacts` (
  `id` INT NOT NULL AUTO_INCREMENT ,
  `login` VARCHAR(45) NOT NULL ,
  `name` VARCHAR(45) NOT NULL ,
  `value` VARCHAR(60) NOT NULL ,
  PRIMARY KEY (`id`) ,
  INDEX `FK_Login_Contacts` (`login` ASC) ,
  UNIQUE INDEX `value_UNIQUE` (`value` ASC) ,
  CONSTRAINT `FK_Login_Contacts`
    FOREIGN KEY (`login` )
    REFERENCES `myblog`.`users` (`login` )
    ON DELETE CASCADE
    ON UPDATE CASCADE)
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_general_ci
COMMENT = 'Таблица с контактными данными пользователей';

или импортировав файл командой:

mysql -uroot -p <contacts.sql

Назначение таблицы понятно из комментариев в скрипте.

Создайте для новой таблицы Entity класс и Session Facade аналогично как это делали в 6 шестой статье с использованием встроенного мастера.

Листинг класса Contacts:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package entity;

import java.io.Serializable;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.xml.bind.annotation.XmlRootElement;

/**
 *
 * @author Egorov A.
 */
@Entity
@Table(name = "contacts")
@XmlRootElement
@NamedQueries({
    @NamedQuery(name = "Contacts.findAll", query = "SELECT c FROM Contacts c"),
    @NamedQuery(name = "Contacts.findById", query = "SELECT c FROM Contacts c WHERE c.id = :id"),
    @NamedQuery(name = "Contacts.findByName", query = "SELECT c FROM Contacts c WHERE c.name = :name"),
    @NamedQuery(name = "Contacts.findByValue", query = "SELECT c FROM Contacts c WHERE c.value = :value")})
public class Contacts implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "id")
    private Integer id;
    @Basic(optional = false)
    @NotNull
    @Size(min = 1, max = 45)
    @Column(name = "name")
    private String name;
    @Basic(optional = false)
    @NotNull
    @Size(min = 1, max = 60)
    @Column(name = "value")
    private String value;
    @JoinColumn(name = "login", referencedColumnName = "login")
    @ManyToOne(optional = false)
    private Users login;

    public Contacts() {
    }

    public Contacts(Integer id) {
        this.id = id;
    }

    public Contacts(Integer id, String name, String value) {
        this.id = id;
        this.name = name;
        this.value = value;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public Users getLogin() {
        return login;
    }

    public void setLogin(Users login) {
        this.login = login;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof Contacts)) {
            return false;
        }
        Contacts other = (Contacts) object;
        if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "entity.Contacts[ id=" + id + " ]";
    }
   
}

Листинг класса ContactsFacade:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package session;

import entity.Contacts;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

/**
 *
 * @author Egorov A.
 */
@Stateless
public class ContactsFacade extends AbstractFacade<Contacts> {
    @PersistenceContext(unitName = "myblogPU")
    private EntityManager em;

    @Override
    protected EntityManager getEntityManager() {
        return em;
    }

    public ContactsFacade() {
        super(Contacts.class);
    }
   
}

Подготовка страницы регистрации

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

Добавьте следующий код перед открывающимся тегом формы:

<div class="text-article">
       <c:if test="${notif ne null}">
             <div class="notif">
                   <span>${notif}</span>
             </div>
       </c:if>
<form method="POST" action="registration">

Теперь если мы из сервлета передадим в запрос атрибут notif то он отобразится как сообщение пользователю.

В таблюцу стилей style.css добавьте правила:

.notif{
    width: 94%;
    margin-left: 2%;
    border: 1px solid #660000;
    background: #ff9999;
    border-radius: 5px;
}
.notif span{
    font-size: 16px;
    font-weight: bold;
    letter-spacing: 1px;
    margin: 5px;
    color: #660033;
}

В настоящих системах так же были бы добавлены javascript'ы для проверки корректности вводимых данных еще на строне клиента, в том числе резидентное обращение к серверу с помощью ajax, но наша цель это познанение Java EE и потому распыляться на сторонние технологии считаю излишним. Проверка корректности данных будет проиходить исключительно на сервере и в случае ошибки будет выводиться то самое сообщение пользователю которое было сделанно выше.

Добавление нового пользователя. Создание сессионного компонента

Как было оговоренно ранее, вся бизнес-логика находится в в сессионых компонентах, поэтому нашей задачей сейчас является:

  1. Собрать пользовательские данные в сервлете.
  2. Проверить их на достаточность и вызвать соотвествующий метод из EJB компонента.
  3. Создать сессионый компонент с реализацией функции добавления нового пользователя.
  4. При добавлении пользователя нужно выполнять все операции в контексте одной транзакции.

Необходимость транзакции обусловленно тем что нам придется добавлять данные сразу в две таблицы users и contacts.

Создадим новый сессионый компонент UsersManager.

  1. Создайте новый файл Кнопка создания нового файла, в списке категорий выберите "Enterprise JavaBean", а в списке типов файлов "Сеансовый компонент":
    Создание сессионого компонента шаг 1
  2. В следующем шаге введите имя EJB компонента UserManager и выбирите тип сеанса без сохранения состояния:
    Создание сессионого компонента шаг 2
  3. IDE создало каркас EJB компонента с одной аннтоацией говорящей что это компонент имеет тип сеанса без сохранения состояния:
    package session;

    import javax.ejb.Stateless;

    /**
     *
     * @author Egorov A.
     */
    @Stateless
    public class UsersManager {

    }
  4. Сразу после имеющийся аннотации добавьте следующию:
    @TransactionManagement(TransactionManagementType.CONTAINER)
    Этой аннотацией мы говорим что все вопросами транзакцией будет заниматься контейнер.
  5. Внутри класса UsersManages нужно создать EntityManager, ровно таким же образом как и в другим EJB компонентах:
    @PersistenceContext(unitName = "myblogPU")
        private EntityManager em;
  6. Далее созданим еще не знакомый нам ресурс SessionContext:
    @Resource
        private SessionContext context;
    SessionContext - класс  реализующий интерфейс EJBContext. Он предоставляет информацию о контейнере, клиенте, использующем компонент и о самом компоненте. Для Entity классов данный интерфейс реализован подклассом EntityContext.

Добавление бизнес-метода

  1. Пришло время добавить новый бизнесс метод, вставьте в тело класса следующий код:
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public Integer addUser(final String login, final String password, final String passwordTwo, final Map<String, String[]> contacts) {
            try{
                if (password.equals(passwordTwo)){
                    Users user=newUser(login, password);
                    newContacts(user,contacts);
                    return 0;
                }else {return 2;}
            }catch (Exception e){
                context.setRollbackOnly();
                e.printStackTrace();
                return 1;
            }
        }
    Объявленный бизнес-метод принемает в качестве параметров логин, пароль и список контактов (мы подразумеваем что можно будет добавлять сколько угодное количество различных контактов за раз).
    Перед методом идет аннотация объявляющая что все действия данной функции будут проходить в рамках одной транзакции. Рассмотрим подробнее возможные аттрибуты транзакции:
    Атрибуты транзакции
    Атрибут Описание
     Required Методы выполняются внутри транзакции. Если клиент ассоциирован с транзакцией, то используется эта транзакция. Если нет, генерируется новая транзакция. Происхоидит коммит в конце метода, который означает, что метод, имеющий включенный атрибут required,  но был вызван когда транзакция уже была начата, не будет вкомичен по завершении метода.
     RequiresNew Методы выполняются внутри транзакции. Если клиент предоставляет транзакцию, она приостанавливается. Если нет – генерируется новая транзакция. Коммит в конце метода. Приостановленная транзакция востонавливается после завершения основной. Используйте этот атрибут если хотите чтобы метод всегда выполнялся в новой транзакции.
     Mandatory Если клиент выполняется внутри транзакции и вызывает метод корпоративного бина, метод выполняется в клиентской транзакции. Если клиент не ассоциирован с транзакцией, контейнер выбрасывает TransactionRequiredException.
     Supports Если клиент ассоциирован с какой-нибудь транзакцией, то метод будет выполняться внутри данной транзакции, если нет... ну её эту транзакцию, без нее все сделает. Атрибут как можно предположить весьма противоречивый и использовать его нужно с осторожностью.
     NotSupported Метод без транзакций. Если клиент ассоциирован с транзакцией то она приостанавливается на время выполнения метода. Если транзакций нет, то новая тоже не создается. Используется для методов не нуждающихся в транзакциях. Позволяет снизить нагрузку на сервер путем исключения транзакций там где она не нужна.
     Never Метод не терпящий транзакций не под каким видом. Если клиент при вызове метода ассоциирован с транзакцией, то выбрасывается исключение RemoteException. Метод выполнится без ошибок только при отсуствие каких либо транзакций у клиента.

     Внути метода мы проверяем пароли на идентичность и после этого выполняем две функции по добавлению нового пользователя и его контактов (их реализация чуть ниже). В случае возникновения исключительной ситуации мы обрекаем весь метод на откат транзакции строкой context.setRollbackOnly();

  2. Теперь реализуем методы newUser и newContacts. Можете воспользоваться помощью IDE и создать каркас методов. Теперь наполним их смыслом:
    private Users newUser(String login, String password) {
            Users user = new Users(login, password);
            em.persist(user);
            return user;
        }
    Из выше приведенного когда нас интересует только метод em.persist(user);, именно он ставит созданный объект класса Users в очередь на добавление в БД, которое произойдет при следующем коммите. Не трудно догадаться что у нас он произойдет при завершение транзакции. Если выполнять этот метод с объектом уже имеющемся в БД то он вызовет исключение javax.persistence.PersistenceException.

    Если при работе понадобится вручную вызвать коммит то это делается командой:
    em.flush();
    еще раз замечу что в нашем случае это не нужно так как все операции выполняются в рамках объявленной транзакции под управлением CMP и по ее завершению коммит (или роллбэк) произойдет автоматически).
    если вы получили некий объект из БД, произвели в нем изменения и хотите записать эти изменения в БД (UPDATE Statement), это делается функцией
    em.merge(object);
    так же надо сказать что эта функция может вести себя по разному: если например Вы выполните эту функцию с объектом которого еще нет в БД, то она автоматически его туда добавит (INSERT Statement).

    Чтобы удалить объект из БД используется функция:
    em.remove(object);
    Обновление объекта из БД проиходит вызовом функции:
    em.refresh(object);
  3. Метод newContacts:
    private void newContacts(Users user, Map<String, String[]> contacts) {
            if (contacts.size()>0){
                for (Map.Entry<String, String[]> entry : contacts.entrySet()) {
                    String key = entry.getKey();
                    String[] values = entry.getValue();
                    for (String value : values) {
                        Contacts contact=new Contacts();
                        contact.setLogin(user);
                        contact.setName(key);
                        contact.setValue(value);
                        em.persist(contact);
                    }
                }
            }
        }
    Здесь мы проверяем что нам передали не пустой Map, открываем цикл по каждому элементу и в нем же цикл параметров. На каждом из этапов создаем новый класс Conctacts. Важно отметить что в метод setLogin мы передаем созданный нами ранее экзепляр класса User.
  4. Взглянув на реализованный нами метод addUser можно предвидеть исключительную ситуацию когда добавляется уже имеющийся узер (одинаковый login). Мы не можем использовать метод merge(user); т.к. он просто обновит данные узера, а это нам совсем не к чему. Необходимо добавить проверку на уникальность логина.
    Мы реализуем эту проверку путем запроса к БД который хранится в entity классе Users. Напомни что при написании аннотации в entity классах есть следующие строки:
    @NamedQueries({
        @NamedQuery(name = "Users.findAll", query = "SELECT u FROM Users u"),
        @NamedQuery(name = "Users.findByLogin", query = "SELECT u FROM Users u WHERE u.login = :login"),
        @NamedQuery(name = "Users.findByPass", query = "SELECT u FROM Users u WHERE u.pass = :pass")})
    Это проименованный список запросов в который, вы, можете добавлять свои. Нас интересует второй по счету запрос при котром параметром поиска является login пользователя.
    Перепешите метод addUser следующим образом:
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
        public Integer addUser(final String login, final String password, final String passwordTwo, final Map<String, String[]> contacts) {
            try{
                if (login!=null && password!=null && passwordTwo!=null && password.equals(passwordTwo)){
                    List resultList = em.createNamedQuery("Users.findByLogin").setParameter("login", login).getResultList();
                    if (resultList.size()==0){
                    Users user=newUser(login, password);
                    newContacts(user,contacts);
                    return 0;
                    }else {return 3;}
                }else {return 2;}
            }catch (Exception e){
                context.setRollbackOnly();
                e.printStackTrace();
                return 1;
            }
        }
    Самая важная строка для нас это:
    List resultList = em.createNamedQuery("Users.findByLogin").setParameter("login", login).getResultList();
    В ней мы обращаемся к методу EntityManager'a createNamedQuery передавая ему названия запроса, далее присваеваем значению параметру логина и получаем результат. Вот так просто мы выполнили запрос к БД.

  5. Дело за малым. Осталось должным образом обработать запрос на регистрацию в нашем сервлете. Откройте файл web_controller
  6. В начале класса объявите новый EJB ресурс:
    @EJB
        UsersManager userManager;
  7. Тело условного оператора который обрабатывает запрос от клиента по адресу /registration будет выглядеть следующим образом:
    if ("/registration".equals(userPath)){
                String login=null,pass=null,pass2=null;
                HashMap<String, String[]> contacts =new HashMap<String, String[]>();
                Enumeration<String> parameters = request.getParameterNames();
                while (parameters.hasMoreElements()) {
                    String parameter = parameters.nextElement();
                    if (parameter.equals("login")){
                        login=request.getParameter(parameter);
                    }else
                    if (parameter.equals("password")){
                        pass=request.getParameter(parameter);
                    }else
                    if (parameter.equals("password2")){
                        pass2=request.getParameter(parameter);
                    }else{
                        contacts.put(parameter, request.getParameterValues(parameter));
                    }
                }
                Integer codeOperation=userManager.addUser(login, pass2, pass2, contacts);
                if (codeOperation!=0)
    {request.setAttribute("notif", "Код завершения операции: "+codeOperation);}
    else
    {request.setAttribute("notif", "Пользователь "+login+" успешно создан!");}
    }
  8. В нем мы так же как и в прошлом обработчике перебираем список параметром от пользователя на наличие нужных нам после этого вызываем созданный нами бизнес метод addUser. Если код завершения операции равен 0 то все прошло успешно и мы сообщаем об этом с радостью нашему пользователю. Сообщение выводится в туже самую страницу регистрации, что конечно в реальном блоге было бы не правильно и для этого нужно создать отдельную страницу, но нам важно научиться пользоваться функционалом javaee, и потому этого будет вполне достаточно.
19.09.2012

Комментарии

Contacts contact=new Contacts(); contact.setLogin(user);contact.setName(key); contact.setValue(value);

почему id не создаем или он автоматически создается?

04.12.2014 Автор: anon