Lista encji z podglądem i edycją – komponent sesyjny
Zarówno JBoss Tools, jak i seam-gen potrafią wygenerować dla encji widoki listy, podglądu i edycji, wraz z odpowiednimi komponentami. Jednak taka obsługa encji daje niewielką elastyczność przy stosunkowo dużej ilości niepotrzebnego kodu. Postanowiłem przygotować własny szablon komponentu pozwalającego na wykonywanie wszelkich operacji na encjach – wyświetlania listy, podglądu, edycji…
Interfejs generyczny
Najpierw przygotuję generyczny interfejs zawierający podstawowe operacje na encjach. Nazwę go EntityConversation (EntityManager, EntityHome i EntityQuery są już zajęte):
public interface EntityConversation<E> {
public String init();
public void createInstance();
public boolean isNewInstance();
public void save();
public void cancel();
public void destroy();
public List<E> getList();
public void setList(List<E> list);
public E getSelectedInstance();
public void setSelectedInstance(
E selectedInstance);
}
Pokrótce wyjaśnię funkcjonalność poszczególnych metod:
init- inicjuje komponent – głównie pobiera listę encji,
createInstance- tworzy nową instancję encji,
isNewInstance- sprawdza, czy wybrana encja jest nowa,
save- zapisuje wybraną encję,
cancel- anuluje edycję encji,
destroy- niszczy komponent sesyjny,
getListisetList- udostępniają właściwość przechowującą listę encji,
getSelectedInstanceisetSelectedInstance- udostępniają właściwość przechowującą wybraną encję.
Szkielet implementacji
Spora część obsługi encji będzie wspólna dla wszystkich komponentów. Dlatego przyda się abstrakcyjna klasa implementująca podstawowe metody interfejsu EntityConversation:
public abstract class AbstractEntityConversation<E>
implements EntityConversation<E> {
}
Na początek dodam właściwości – listę encji:
private List<E> list;
public List<E> getList() {
return list;
}
public void setList(List<E> list) {
this.list = list;
}
oraz wybraną instancję encji:
private E selectedInstance;
public E getSelectedInstance() {
return selectedInstance;
}
public void setSelectedInstance(
E selectedInstance) {
this.selectedInstance = selectedInstance;
}
Przyda mi się także komponent obsługujący wymianę informacji z bazą danych:
@PersistenceContext protected EntityManager entityManager;
Ścieżkę do domyślnej strony dla encji oraz zapytanie pobierające listę encji ustawię w implementacjach konkretnych. Utworzę do tego celu dwie metody abstrakcyjne:
protected abstract String getDefaultView(); protected abstract Query getQuery();
Metoda init będzie rozpoczynać konwersację (lub kontynuować już istniejącą), pobierać listę encji z zapytania (linijka 4) oraz przekierowywać na właściwą stronę (5):
@Begin(join = true)
@SuppressWarnings("unchecked")
public String init() {
setList(getQuery().getResultList());
return getDefaultView();
}
Wybrana instancja jest nowa jeżeli istnieje oraz nie występuje na liście encji:
public boolean isNewInstance() {
if (getSelectedInstance() == null) {
return false;
}
return !getList().contains(getSelectedInstance());
}
Zapisanie wybranej instancji to jej utworzenie w bazie (jeżeli jest nowa) lub uaktualnienie:
public void save() {
if (isNewInstance()) {
persist();
} else {
update();
}
}
Anulowanie zapisania encji polegać będzie na wybraniu pierwszej encji z listy – dzięki temu usunięta zostanie ewentualna nowa instancja:
public void cancel() {
setSelectedInstance(getList().get(0));
}
Po dodaniu do bazy (linia 2) nowa encja musi zostać też wstawiona do listy encji (3):
protected void persist() {
entityManager.persist(getSelectedInstance());
getList().add(getSelectedInstance());
}
Natomiast aktualizacja encji wygląda następująco:
protected void update() {
setSelectedInstance(entityManager.merge(
getSelectedInstance()));
}
Podczas niszczenia komponentu sesyjnego bieżąca konwersacja będzie kończona:
@Remove
@End(beforeRedirect = true)
public void destroy() {
}
Interfejs lokalny
Każdy komponent sesyjny potrzebuje interfejsu lokalnego (lub zdalnego, ale w tym wypadku lokalnego), który będzie zawierał wszystkie metody używane przez widok (czyli plik JSF):
@Local
public interface Users
extends EntityConversation<User> {
}
Jak widać, interfejs ten po prostu uściśla interfejs generyczny EntityConversation, przypisując określony typ encji (w przykładzie jest to użytkownik, czyli User).
Implementacja komponentu
Implementacją interfejsu Users będzie klasa UsersBean. Będzie to komponent stanowy o zasięgu konwersacji o nazwie users:
@Name("users")
@Stateful
@Scope(ScopeType.CONVERSATION)
public class UsersBean
extends AbstractEntityConversation<User>
implements Users {
}
Do tej pory nie zostały zaimplementowane jeszcze trzy metody. Pierwsza z zwraca ścieżkę do strony JSF:
protected String getDefaultView() {
return "/adminTools/users.xhtml";
}
Kolejna metoda tworzy nowe zapytanie. Może to być zapytanie z parametrami lub bez, podane wprost… w tym wypadku jest to nazwane zapytanie z klasy User:
protected Query getQuery() {
return entityManager.createNamedQuery("allUsers");
}
Ostatnia metoda tworzy nową instancję encji:
public void createInstance() {
setSelectedInstance(new User());
}
Podsumowanie
Dzisiaj opisałem komponent sesyjny oraz cały kod Java. Następnym razem wyjaśnię, jak połączyć całość z widokiem.



