Статьи

Полное веб-приложение JSF EJB JPA JAAS — Часть 1

Этот пост будет самым большим в моем блоге! Мы увидим полное веб-приложение. Это будет сделано с помощью новейших технологий (до сегодняшнего дня), но я дам несколько советов, чтобы показать, как адаптировать этот пост к старым технологиям.

В конце этого поста вы найдете исходный код для скачивания. Вы можете использовать его как хотите. Просто перейдите на последнюю страницу и выполните загрузку. \ о /

Если вы скачали код и ничего не поняли, в этом посте я объясню каждую деталь, найденную в коде. Просто прочитайте тему в этом посте, который вы хотите.

Я перечислю ниже технологии, которые я буду использовать в этом посте:

  • JSF 2.0 Mojarra — с ManagedBeans в качестве RequestScope и SessionScope.
  • Интернационализация сообщений — файл, который будет содержать все сообщения нашей системы; вам будет легче переводить страницы.
  • CSS-файл по умолчанию, который будет импортирован как библиотека.
  • EJB 3 — Наши DAO и Фасады будут @Stateless.
  • Универсальный DAO — универсальный DAO, в котором будут использоваться действия CRUD для облегчения нашей жизни.
  • JPA 2 — Для отображения наших классов в БД
  • JAAS — для контроля входа и доступа пользователей к страницам.
  • MVC — я буду использовать этот шаблон с небольшими изменениями.
  • Postgres как база данных, но я покажу, как настроить приложение для MySQL.

Я не буду использовать TDD-JUnit для тестирования наших View / Model / Classes, но по следующей ссылке вы можете увидеть методику использования JUnit для тестирования ваших ManagedBeans: JUnit с HSQLDB, JPA и Hibernate .

Инструменты, которые мы будем использовать:

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

Я не буду кодировать интерфейс моей модели / DAO, просто чтобы сэкономить место. Помните, что вы всегда должны кодировать интерфейсы ( Design Pattern — Strategy ) .

Прежде чем продолжить, убедитесь, что вы установили JBoss Tools и JBoss 7 именно в таком порядке.

Бизнес модель

Давайте создадим проект EJB, который будет поддерживать наш системный бизнес.

Нажмите на кнопку « Готово ».

Давайте создадим классы User и Dog, которые будут внутри пакета «com». Это будет иметь код ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package com.model;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
 
@Entity
@Table(name = 'USERS')
@NamedQuery(name='User.findUserByEmail', query='select u from User u where u.email = :email')
public class User {
 
 public static final String FIND_BY_EMAIL = 'User.findUserByEmail';
 
 @Id
 @GeneratedValue(strategy=GenerationType.AUTO)
 private int id;
 
 @Column(unique = true)
 private String email;
 private String password;
 private String name;
 private String role;
 
 public int getId() {
  return id;
 }
 
 public void setId(int id) {
  this.id = id;
 }
 
 public String getEmail() {
  return email;
 }
 
 public void setEmail(String email) {
  this.email = email;
 }
 
 public String getPassword() {
  return password;
 }
 
 public void setPassword(String password) {
  this.password = password;
 }
 
 public String getName() {
  return name;
 }
 
 public void setName(String name) {
  this.name = name;
 }
 
 public String getRole() {
  return role;
 }
 
 public void setRole(String role) {
  this.role = role;
 }
 
 @Override
 public int hashCode() {
  return getId();
 }
 
 @Override
 public boolean equals(Object obj) {
  if(obj instanceof User){
   User user = (User) obj;
   return user.getEmail().equals(getEmail());
  }
 
  return false;
 }
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.model;
 
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
 
@Entity
@Table(name = 'DOGS')
public class Dog {
 
 @Id
 @GeneratedValue(strategy = GenerationType.AUTO)
 private int id;
 private String name;
 private double weight;
 
 public int getId() {
  return id;
 }
 
 public void setId(int id) {
  this.id = id;
 }
 
 public String getName() {
  return name;
 }
 
 public void setName(String name) {
  this.name = name;
 }
 
 public double getWeight() {
  return weight;
 }
 
 public void setWeight(double weight) {
  this.weight = weight;
 }
 
 @Override
 public int hashCode() {
  return getId();
 }
 
 @Override
 public boolean equals(Object obj) {
 
  if(obj instanceof Dog){
   Dog dog = (Dog) obj;
   return dog.getId() == getId();
  }
 
  return false;
 }
}

О коде выше:

  • У класса User есть поле с именем «role», в котором будет храниться уровень роли пользователя. Я создал одно поле, и мы оставим все данные в одной таблице, чтобы их было легче понять. Если вы хотите получить более подробную информацию о JAAS, вы можете написать в этом посте: Проверка входа пользователя с помощью JAAS и JSF .
  • Я позволю JPA обрабатывать таблицы Id поколений. Если вы хотите изменить способ создания идентификатора, вы можете проверить эти сообщения, чтобы увидеть, как это сделать: JPA SequenceGenerator , JPA TableGenerator — Простой ключ Primay .
  • Электронная почта будет уникальной; это будет идентификатор входа в систему.
  • Обратите внимание, что класс, который должен быть объявлен как Entity, нуждается только в следующих аннотациях: «@Entity» и «@Id». Класс не должен реализовывать интерфейс Serializable.

Бизнес — ДАО

Я буду использовать общий DAO для основных операций CRUD, а другие два DAO: один для пользователя и другой для собаки. Понять его использование будет очень легко:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package com.dao;
 
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
 
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaQuery;
 
public abstract class GenericDAO<T> {
 private final static String UNIT_NAME = 'CrudPU';
 
 @PersistenceContext(unitName = UNIT_NAME)
 private EntityManager em;
 
 private Class<T> entityClass;
 
 public GenericDAO(Class<T> entityClass) {
  this.entityClass = entityClass;
 }
 
 public void save(T entity) {
  em.persist(entity);
 }
 
 public void delete(T entity) {
  T entityToBeRemoved = em.merge(entity);
 
  em.remove(entityToBeRemoved);
 }
 
 public T update(T entity) {
  return em.merge(entity);
 }
 
 public T find(int entityID) {
  return em.find(entityClass, entityID);
 }
 
 // Using the unchecked because JPA does not have a
 // em.getCriteriaBuilder().createQuery()<T> method
 @SuppressWarnings({ 'unchecked', 'rawtypes' })
 public List<T> findAll() {
  CriteriaQuery cq = em.getCriteriaBuilder().createQuery();
  cq.select(cq.from(entityClass));
  return em.createQuery(cq).getResultList();
 }
 
 // Using the unchecked because JPA does not have a
 // ery.getSingleResult()<T> method
 @SuppressWarnings('unchecked')
 protected T findOneResult(String namedQuery, Map<String, Object> parameters) {
  T result = null;
 
  try {
   Query query = em.createNamedQuery(namedQuery);
 
   // Method that will populate parameters if they are passed not null and empty
   if (parameters != null && !parameters.isEmpty()) {
    populateQueryParameters(query, parameters);
   }
 
   result = (T) query.getSingleResult();
 
  } catch (Exception e) {
   System.out.println('Error while running query: ' + e.getMessage());
   e.printStackTrace();
  }
 
  return result;
 }
 
 private void populateQueryParameters(Query query, Map<String, Object> parameters) {
 
  for (Entry<String, Object> entry : parameters.entrySet()) {
   query.setParameter(entry.getKey(), entry.getValue());
  }
 }
}
01
02
03
04
05
06
07
08
09
10
11
12
13
package com.dao;
 
import javax.ejb.Stateless;
 
import com.model.Dog;
 
@Stateless
public class DogDAO extends GenericDAO<Dog> {
 
 public DogDAO() {
  super(Dog.class);
 }
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.dao;
 
import java.util.HashMap;
import java.util.Map;
 
import javax.ejb.Stateless;
 
import com.model.User;
 
@Stateless
public class UserDAO extends GenericDAO<User> {
 
 public UserDAO() {
  super(User.class);
 }
 
 public User findUserByEmail(String email){
  Map<String, Object> parameters = new HashMap<String, Object>();
  parameters.put('email', email); 
 
  return super.findOneResult(User.FIND_BY_EMAIL, parameters);
 }
}

О коде выше:

  • Я скрыл некоторые предупреждения, потому что код JPA еще не «понимает» дженерики.
  • Метод findOneResult имеет защищенный доступ только для предотвращения внешнего доступа из других классов; этот метод требует логики для заполнения параметров, как мы видим в UserDAO.
  • Класс GenericDAO имеет полные методы CRUD плюс метод, который возвращает один объект с заданным NamedQuery.
  • У класса UserDAO есть метод (findUserByEmail), который принадлежит только классу; но он имеет все методы CRUD по наследству. С этим шаблоном DAO мы получили более гибкий код.
  • В DogDAO нет никакого метода, а есть только методы CRUD; Вы можете реализовать любой метод в классе без проблем.
  • Вместо того, чтобы использовать метод для «сохранения» и другой для «обновления» ваших объектов, вы можете использовать один метод «entityManager.merge ()». У вас будет тот же результат, но вам нужно будет обратить внимание на параметры каскада.
  • Я не использовал интерфейсы, потому что EJB 3.1 позволяет нам иметь Локальные Сессионные Компоненты без Состояния без интерфейса. Если вы используете более старую версию EJB, вам необходимо реализовать интерфейс (как реализовать EJB с интерфейсами, мы увидим в этом посте на странице «Фасады»). Я не буду разрабатывать интерфейсы моей DAO / Model только для экономии места в этом посте. Помните: «Всегда программируйте на интерфейс» ( Design Pattern — Strategy ).
  • Если вы используете JBoss 4.2, вы можете использовать аннотации org.jboss.annotation.ejb.LocalBinding или org.jboss.annotation.ejb.RemoteBinding; в этой аннотации вы можете написать имя, которое будет отображено и передано EJB.

Бизнес — Фасады

Я создам Фасады, которые станут «мостом» между View и DAO. На Фасаде мы оставим все бизнес-правила, оставив в DAO только функции / транзакции «базы данных», например, CRUD и запросы.

Давайте посмотрим, какими будут наши классы UserFacade и DogFacade:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
package com.facade;
 
import java.util.List;
 
import javax.ejb.Local;
 
import com.model.Dog;
 
@Local
public interface DogFacade {
 
 public abstract void save(Dog dog);
 
 public abstract Dog update(Dog dog);
 
 public abstract void delete(Dog dog);
 
 public abstract Dog find(int entityID);
 
 public abstract List<Dog> findAll();
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.facade;
 
import java.util.List;
 
import javax.ejb.EJB;
import javax.ejb.Stateless;
 
import com.dao.DogDAO;
import com.model.Dog;
 
@Stateless
public class DogFacadeImp implements DogFacade {
 
 @EJB
 private DogDAO dogDAO;
 
 @Override
 public void save(Dog dog) {
  isDogWithAllData(dog);
 
  dogDAO.save(dog);
 }
 
 @Override
 public Dog update(Dog dog) {
  isDogWithAllData(dog);
 
  return dogDAO.update(dog);
 }
 
 @Override
 public void delete(Dog dog) {
  dogDAO.delete(dog);
 }
 
 @Override
 public Dog find(int entityID) {
  return dogDAO.find(entityID);
 }
 
 @Override
 public List<Dog> findAll() {
  return dogDAO.findAll();
 }
 
 private void isDogWithAllData(Dog dog){
  boolean hasError = false;
 
  if(dog == null){
   hasError = true;
  }
 
  if (dog.getName() == null || ''.equals(dog.getName().trim())){
   hasError = true;
  }
 
  if(dog.getWeight() <= 0){
   hasError = true;
  }
 
  if (hasError){
   throw new IllegalArgumentException('The dog is missing data. Check the name and weight, they should have value.');
  }
 }
}
01
02
03
04
05
06
07
08
09
10
package com.facade;
 
import javax.ejb.Local;
 
import com.model.User;
 
@Local
public interface UserFacade {
 public User findUserByEmail(String email);
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
package com.facade;
 
import javax.ejb.EJB;
import javax.ejb.Stateless;
 
import com.dao.UserDAO;
import com.model.User;
 
@Stateless
public class UserFacadeImp implements UserFacade {
 
 @EJB
 private UserDAO userDAO;
 
 public User findUserByEmail(String email) {
  return userDAO.findUserByEmail(email);
 }
}

О коде выше:

  • Класс DogFacadeImp выполняет всю работу по защите DogDAO от представления. Он может иметь бизнес-правила и избегать доступа к базе данных, если некоторые данные отсутствуют или любое другое бизнес-правило нарушено.
  • У UserFacade есть только один метод, потому что в этом посте у нас не будет User crud; bean-компонент будет искать пользователя только в том случае, если представление не находит его в HttpSession.
  • Если вы используете JBoss 4.2, вы можете использовать аннотации org.jboss.annotation.ejb.LocalBinding или org.jboss.annotation.ejb.RemoteBinding; в этой аннотации вы можете написать имя, которое будет отображено и передано EJB.
  • Я делаю проверку в DogFacadeImp, чтобы убедиться, что у собаки есть только действительные данные. Помните, что каждый может отправить вам неверные данные, и проверка вашего представления может работать не так, как вы ожидаете. Данные вашего заявления очень важны, и двойная проверка всегда стоит.

Интерфейсы аннотированы @Local, но я помню, что эта аннотация не является обязательной. Если вы не напишите аннотацию @Local, ваш сервер будет считать, что ваш EJB является локальным по умолчанию.

Бизнес — Источник данных (по модулям)

Нам также нужно настроить наш источник данных.

Сначала я попытался создать источник данных, следуя этому руководству http://community.jboss.org/wiki/JBossAS7-DatasourceConfigurationForPostgresql (я следую учебным пособиям, как и любой другой человек), но после развертывания с использованием нескольких ошибок началось EJB и JBoss не смогли найти банку Postgres.

Если вы используете JBoss 6 или любую другую версию, вам не нужно создавать модуль; просто поместите файл в папку «default / lib». Если у вас есть какие-либо сомнения по поводу настройки источника данных, я покажу, как это сделать, в JBoss 6 или другой версии под ним, здесь: Проверка входа пользователя с помощью JAAS и JSF .

Давайте создадим модуль Postgres. Внутри вашего JBoss 7 создайте каталог: « YOUR_JBOSS / modules / org / postgresql / main ». Скопируйте банку в созданный каталог и создайте файл с именем «module.xml»; скопируйте код ниже внутри файла «module.xml»:

1
2
3
4
5
6
7
<?xml version='1.0' encoding='UTF-8'?>
<module xmlns='urn:jboss:module:1.0' name='org.postgresql'>
 <resources>
  <resource-root path='postgresql-9.1-901.jdbc4.jar'/>
 </resources>
 <dependencies><module name='javax.api'/></dependencies>
</module>

Обратите внимание, что внутри файла «module.xml» написано имя файла jar, имя должно совпадать с именем файла jar Postgres.

Для создания модуля MySQL создайте следующую папку « YOUR_JBOSS / modules / com / mysql / main » . Скопируйте банку в созданный каталог и создайте файл с именем «module.xml»; скопируйте код ниже внутри файла «module.xml»:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
<?xml version='1.0' encoding='UTF-8'?>
 
<!--
  ~ JBoss copyrights
  -->
 
<module xmlns='urn:jboss:module:1.0' name='com.mysql'>
  <resources>
    <resource-root path='mysql-connector-java-5.1.15.jar'/>
  </resources>
  <dependencies>
    <module name='javax.api'/>
  </dependencies>
</module>

Давайте отредактируем файл « YOUR_JBOSS / standalone / configuration / standalone.xml ». Внутри ключа «<источники данных>» добавьте код ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<datasources>
 <!-- Add this config: begin -->
 <datasource jndi-name='CrudDS' pool-name='CrudDS_Pool' enabled='true' jta='true' use-java-context='true' use-ccm='true'>
  <connection-url>jdbc:postgresql://localhost:5432/CrudDB</connection-url>
  <driver-class>org.postgresql.Driver</driver-class>
  <driver>postgresql-jdbc4</driver>
  <pool>
   <min-pool-size>2</min-pool-size>
   <max-pool-size>20</max-pool-size>
   <prefill>true</prefill>
   <use-strict-min>false</use-strict-min>
   <flush-strategy>FailingConnectionOnly</flush-strategy>
  </pool>
  <security>
   <user-name>postgres</user-name>
   <password>postgres</password>
  </security>
  <validation>
   <check-valid-connection-sql>SELECT 1</check-valid-connection-sql>
   <validate-on-match>false</validate-on-match>
   <background-validation>false</background-validation>
   <use-fast-fail>false</use-fast-fail>
  </validation>
 </datasource>
 <!-- Add this config: end -->
 <drivers>
  <!-- Add this config: begin -->
  <driver name='postgresql-jdbc4' module='org.postgresql'/>
  <!-- Add this config: end -->
 </drivers>

Чтобы настроить источник данных с MySQL, посмотрите здесь:
http://community.jboss.org/wiki/DataSourceConfigurationInAS7

Бизнес — XML-конфигурации

Давайте посмотрим, каким будет наш файл persistence.xml (этот файл должен находиться внутри папки src / META-INF):

01
02
03
04
05
06
07
08
09
10
<?xml version='1.0' encoding='UTF-8'?>
    <persistence-unit name='CrudPU' transaction-type='JTA'>
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <jta-data-source>java:/CrudDS</jta-data-source>
        <properties>
            <property name='hibernate.hbm2ddl.auto' value='update'/>
        </properties>
    </persistence-unit>
</persistence>

Мы получили очень простой код; он просто указывает на источник данных и возможность генерировать все таблицы базы данных с помощью «update».

Добавьте проект EJB в JBoss.

Создайте базу данных в Postgres и запустите JBoss; после того, как вы запустили его, JPA создаст таблицы.

Проверьте изображение ниже, чтобы увидеть таблицы / последовательности, которые JPA создал для нас.

Создайте файл с именем «jboss-web.xml» в папке WEB-INF и напишите в нем приведенный ниже код:

1
2
3
4
5
6
7
8
9
<?xml version='1.0' encoding='UTF-8'?>
 
<jboss-web>
 <!-- URL to access the web module -->
 <context-root>CrudJSF</context-root>
 
 <!-- Realm that will be used -->
 <security-domain>java:/jaas/CrudJSFRealm</security-domain>
</jboss-web>

В приведенном выше файле мы настроили Царство, которое будет использовать наше приложение. Давайте вставим пользователей в базу данных, которая будет выполнять вход с помощью JAAS (если вы хотите увидеть более подробную информацию о JAAS, вы можете увидеть это здесь: Проверка входа пользователя с помощью JAAS и JSF ). Я не буду вдаваться в подробности JAAS, потому что вы можете найти каждый шаг, описанный в предыдущей ссылке.

Смотрите на изображении ниже данные, которые я вставил вручную в базу данных (вы должны сделать то же самое):

Перейдите ко второй части урока.

Ссылка: полное веб-приложение JSF EJB JPA JAAS от нашего партнера JCG Хеберта Коэльо в блоге uaiHebert .