niedziela, 7 lutego 2010

perwszy test z Apache JMeter

Temat testów wydajnościowych pojawia się od czasu do czasu w waszych komentarzach, mailach lub wiadomościach GG. Dzisiaj zaspokoję ciekawość tych, którzy chcieliby zacząć a nie bardzo wiedzą jak. Przedstawię przepis na prosty test wydajnościowy.

Składniki:
- przeglądarka WWW (u mnie będzie to Opera)
- Apache JMeter
- testowana aplikacja WWW

Przepis umożliwia testowanie każdego rodzaju aplikacji webowych. W moim przypadku będzie to prosta witryna składająca się z dwóch statycznych stron HTML.
a.html

<html>
<body>
<h1>Serwus!</h1>
<a href = b.html>dalej</a>
</body>
</html>

i b.html

<html>
<body>
<h1>Druga strona</h1>
</body>
</html>


Uruchamiam te strony na serwerze Apache Tomcat.


Działa. Pora na JMetera. Dystrybuowany jest on jako archiwum zip lub tgz, więc z jego instalacją i uruchomieniem nie będzie problemu.
Posługuję się wersją 2.3.1.


W JMeterze można tworzyć testy na kilka sposobów. Najwygodniej jest je nagrać w przeglądarce WWW. Odbywa się to za pośrednictwem specjalnego serwera proxy. Dodaję go klikając prawym przyciskiem myszy na węźle WorkBench


i wybierając z podręcznego menu Add / Non-Test Elements / Http Proxy Server
Port zmieniam na 9999 (na 8080 jest uruchomiona moja aplikacja).


W sekcji URL Patterns to include dodaję wyrażenie regularne .*\.html. Dzięki temu w moim teście znajdą się tylko strony HTML. Warto dodać tutaj tylko strony generowane dynamiczne (jsp, php, jsf) ponieważ na sprawdzaniu ich wydajności najbardziej nam zależy.


Do węzła Test Plan dodaję grupę wątków Add / Thread Group. Tam zapiszą się nagrane żądania do mojej aplikacji.


Następnie uruchamiam serwer proxy klikając przycisk Start



Uruchamiam przeglądarkę WWW w której konfiguruję serwer proxy



i nagrywam test klikając w uruchomioną aplikację


Nagrany scenariusz znajduje się w grupie wątków. W moim przypadku składa się on z dwóch stron /test-app/a.html i /test-app/b.html.


Pora na sprawdzenie wydajności aplikacji. W ustawieniach grupy wątków zwiększam wartość Number of Threads (users) do 3.. Loop Count ustawiam na 20. Odpowiada to trzem użytkownikom, z których każdy 20 razy wykona scenariusz testowy.


Do swojej grupy wątków dodaję węzeł "raport z testów" (Add / Listener / Summary Report) w którym będę podglądał wyniki testów.


To w zasadzie wszystko. Zapisuję test i uruchamiam go wybierając akcję Start z menu Run.


Hm... Średnio strona mojej aplikacji generuje się 1 milisekundę. Całkiem nieźle.
Planowałem powtórzyć ten test na serwerze Apache WWW, ale wobec takich wyników na Tomcacie nie będzie to miało chyba większego sensu.

Stworzony plan testów zamieszczam tutaj.

niedziela, 17 stycznia 2010

SQuirreL SQL Hibernate plugin

SQuirreL SQL jest moim ulubionym klientem SQL. Umożliwia on również, wykonywanie zapytań HQL. Wtyczka udostępniające tę funkcjonalność dostępna jest w wersji 0.01, ale działa całkiem przyzwoicie. Podpowiada nazwy obiektów oraz ich właściwości. Umożliwia tłumaczenie zapytania HQL na zapytanie SQL oraz jego wykonanie.

Ponieważ sporo trudności przedstawia jej konfiguracja, opisze ją na przykładzie aplikacji wykorzystującej Spring Framework. Zakładam, że połączenie SQuirreL SQLa do bazy danych jest już skonfigurowane.

Wtyczka dystrybuowana jest wraz z instalacyjną wersją klienta SQuirreL SQL. Fakt jej zainstalowania można zweryfikować w menu Plugins/Summary.


Zgodnie z dokumentacją w pliku plugins/hibernate/readme.html wtyczce musimy dostarczyć obiekt klasy SessionFactoryImpl wskazując metodę o sygnaturze
public org.hibernate.impl.SessionFactoryImpl getSessionFactoryImpl()
która go udostępni.

Ponieważ moja aplikacja korzysta ze Springa, obiekt SessionFactoryImpl najlepiej pobrać z jego kontekstu. Klasa implementująca powyższą metodę wygląda zatem:

package pl.matt.hibernate;

import org.hibernate.impl.SessionFactoryImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class SessionFactoryProvider {

public org.hibernate.impl.SessionFactoryImpl getSessionFactoryImpl() {
try {
ApplicationContext ac = new FileSystemXmlApplicationContext("//home/mateusz/priv/workspace/blog/resources/application-context.xml");
SessionFactoryImpl out = (SessionFactoryImpl) ac.getBean("sessionFactory");
return out;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

}

Blok try-catch jest zbędny, ale ułatwia diagnozowanie problemów przy uruchamianiu wtyczki pod warunkiem uruchomienia klienta SQuirreL SQL z wiersza poleceń.
Ścieżka do pliku ze Springowym kontekstem aplikacji poprzedzona jest podwójnym ukośnikiem, gdyż pierwszy z nich jest wycinany przez Springa.

To tyle jeżeli chodzi o kodowanie. Czas na wskazanie powyższej klasy we właściwościach SQuirreL SQLa. Otwieram zakładkę Hibernate z menu File/Global Preferencess, gdzie tworzę nową konfigurację przyciskiem New.

Wpisuję jej nazwę a następnie konfiguruję ścieżkę klas przyciskiem Add classpath entry. Wskazana ścieżka musi zawierać zarówno skompilowane klasy aplikacji jak i wymagane przez nią biblioteki.

Następnie zaznaczam opcję: Invoke the user defined provider method below i wskazuję napisaną niedawno klasę.


To tyle. Po naciśnięciu przycisku connect na zakładce Hibernate możemy już wykonywać zapytania HQL. Polecam zaznaczenie opcji Always format (formatowanie wynikowego SQLa) oraz Execute SQL (bez niej HQL zostanie jedynie przetłumaczony do SQLa) i można się już mierzyć z HQLem.

niedziela, 10 stycznia 2010

Hibernate NamingStrategy

Na jednym z ostatnich spotkań WJUGa pożaliłem się odrobinie osobie siedzącej po mojej prawej stronie na Hibernate. Były to żale bardziej estetyczne niż funkcjonalne a dotyczyły nazw generowanych przez to właśnie narzędzie ORM. Nie podobały mi się ani nazwy tabel ani kolumn ani kluczy obcych jakie nadaje Hibernate podczas tworzenia schematu bazy danych. Osoba słuchająca moich marudzeń powiedziała krótko i fachowo: "Obczaj naming strategy". Obczaiłem.

Domyślnie Hibernate tworzy nazwę tabeli taką samą jak nazwa klasy - encji, natomiast nazwę kolumny taką samą jak nazwa pola. Nazwy kluczy obcych generowane są w sposób nieokreślony: np. 'FK2F894B2DC512614D'.

Ja chciałbym, aby nazwy kolumn i tabel utrzymane były w konwencji z podkreśleniem (czyli np. user_data, default_color), nazwy tabel występowały w liczbie mnogiej, a nazwy kluczy obcych były jakkolwiek zrozumiałe (np. FK_EMP_COM dla klucza łączącego tabelę EMPLOYEES z COMPANIES).

Pierwsze spojrzenie na interfejs NamingStrategy pozbawiło mnie złudzeń. O czytelnych nazwach kluczy obcych mogę sobie pomarzyć. Wybrana strategia stosowana jest tylko do nazw kolumn i tabel. Dobre i to.

Szybki przegląd implementacji:

DefaultNamingStrategy
EJB3NamingStratefy
DefaultComponentSafeNamingStratefy
ImprovedNamingStrategy

wykazał, że trzy pierwsze odpadają w przedbiegach. Konwencja wielbłądzia zachowana. Z klasy UserData.java tworzy się tabela UserData.

Cała nadzieja w ImprovedNamingStrategy. Konwertuje ona nazwy na konwencję z podkreśleniem... i tyle. O liczbie mnogiej mogę póki co pomarzyć. Nie tylko z resztą ja więc chyba skończy się na napisaniu swojej implementacji.

Nazwy kluczy obcych pozostają nie ruszone. Nawet nie nagryzione. Jeżeli wiesz, jak można je zmienić, daj koniecznie znać.

niedziela, 27 grudnia 2009

dzlaczego nie lubię Mavena, cz. I

W świecie sposobów budowania aplikacji javowych królują 2 rozwiązania: Apache Ant i Maven2. Od jakiegoś czasu uczestniczę w sporym projekcie opartym o to drugie rozwiązanie. O ile Maven (1) miał opinie rozwiązania nie do końca dopracowanego, o tyle głosy na temat Mavena2 są w większości pochlebne: bo lepszy, bo nowszy, bo deklaratywny, bo sam martwi się o zależności.
Teoretycznie wszystko wygląda ładnie, a w praktyce...
... a w praktyce potrzebuję skorzystać z biblioteki XML-RPC Server. Co robię w takim przypadku?
Wchodzę na stronę http://mvnrepository.com/
i wpisuję nazwę szukanej biblioteki.
Coś znalazłem, więc dodaję zależność do pliku pom.xml

<dependencies>
<dependency>
<groupId>org.apache.xmlrpc</groupId>
<artifactId>xmlrpc-server</artifactId>
<version>3.1.2</version>
</dependency>
</dependencies>

i kompiluję projekt, a tu niespodzianka:

[INFO] ------------------------------------------------------------------------
[ERROR] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Compilation failure
error: error reading /home/mateusz/.m2/repository/javax/servlet/servlet-api/2.3/servlet-api-2.3.jar; error in opening zip file


Hm... chwila refleksji... i nie wiadomo o co chodzi. Z pomocą przychodzi cat

cat ~/.m2/repository/javax/servlet/servlet-api/2.3/servlet-api-2.3.jar
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="http://download.java.net/maven/1/javax.servlet/jars/servlet-api-2.3.jar">here</a>.</p>
<hr>
<address>Apache Server at maven-repository.dev.java.net Port 443</address>
</body></html>


No tak... jar to to nie jest.
Aby skompilować projekt, trzeba usunąć ten tekstowy "jar":

rm ~/.m2/repository/javax/servlet/servlet-api/2.3/servlet-api-2.3.jar


i dodać w pliku pom.xml repozytorium


<repositories>
<repository>
<id>java.net</id>
<name>java.net</name>
<url>http://download.java.net/maven/1</url>
<layout>legacy</layout>
</repository>
</repositories>


Cóż, nowe możliwości, nowe problemy... ale przy servlet API? A może to z moim podejściem do mavena jest coś nie tak? Przyznam się, że w głównej mierze polega ono na rozpoznaniu walką.

sobota, 31 października 2009

O testach wydajnościowych słów kilka

Testy wydajnościowe, jak to testy... robić trzeba. Jak to pisze Misko: You do test, right? Zazwyczaj aby je przeprowadzić korzystałem z Apache JMeter. Polecam ten program i może nawet kiedyś zamieszczę prosty przewodnik, jak zacząć z nim przygodę. Dzisiaj jednak skupię się na innym problemie i innym narzędziu.
Przeprowadzając testy wydajnościowe, najważniejszym w moim odczucie problemem, z jakim się spotyka osoba pisząca test jest odwzorowanie charakterystyki żądań obsługiwanych przez aplikację. Tak przy okazji, wiedzieliście, że aplikacja to "ozdobny wzór naszywany na tło z innego materiału"? A spieszyć to "uczynić kogoś pieszym" Coraz bardziej lubię słowniki.

A priori trudno określić, jak użytkownicy będą korzystać z naszego programu i w co będą klikać. Kiedy będą żądane obrazki, kiedy pliki css i różne inne. Obserwując jednak to, co się dzieje na serwerze, możemy utworzyć całkiem sprawne scenariusze.

A co się dzieje, możemy odczytać z logów. Zakładam tutaj, że ruch sieciowy obsługiwany jest przez serwer WWW Apache, za którym stoi serwer aplikacji, z naszą aplikacją internetową.
Apache trzyma informacje o tym kto i do jakich zasobów wysyła żądania w pliku access.log.
Podglądam jego zawartość poleceniem
tail -f /var/logs/apache2/access.log


83.2.107.220 - - [31/Oct/2009:23:03:08 +0100] "GET /swiat/komentarze/_resource/fs/img/ajax_loading_small.gif HTTP/1.0" 301 284 "http://beta.polityka.pl/swiat/komentarze/1500387,1,po-unijnym-szczycie.read" "Opera/9.80 (Windows NT 5.1; U; pl) Presto/2.2.15 Version/10.00" 0/338
83.2.107.220 - - [31/Oct/2009:23:03:08 +0100] "GET /swiat/komentarze/_resource/fs/res/img-blogi.png HTTP/1.0" 301 276 "http://beta.polityka.pl/swiat/komentarze/1500387,1,po-unijnym-szczycie.read" "Opera/9.80 (Windows NT 5.1; U; pl) Presto/2.2.15 Version/10.00" 0/246
83.2.107.220 - - [31/Oct/2009:23:03:08 +0100] "GET /swiat/komentarze/_resource/res/20000457/cust/f120x80 HTTP/1.0" 200 4025 "http://beta.polityka.pl/swiat/komentarze/1500387,1,po-unijnym-szczycie.read" "Opera/9.80 (Windows NT 5.1; U; pl) Presto/2.2.15 Version/10.00" 0/1362
83.2.107.220 - - [31/Oct/2009:23:03:08 +0100] "GET /swiat/komentarze/_resource/fs/img/presseurop.png HTTP/1.0" 301 275 "http://beta.polityka.pl/swiat/komentarze/1500387,1,po-unijnym-szczycie.read" "Opera/9.80 (Windows NT 5.1; U; pl) Presto/2.2.15 Version/10.00" 0/337
83.2.107.220 - - [31/Oct/2009:23:03:08 +0100] "GET /swiat/komentarze/_resource/fs/img/polityka_logo_small.png HTTP/1.0" 301 280 "http://beta.polityka.pl/swiat/komentarze/1500387,1,po-unijnym-szczycie.read" "Opera/9.80 (Windows NT 5.1; U; pl) Presto/2.2.15 Version/10.00" 0/264
83.2.107.220 - - [31/Oct/2009:23:03:08 +0100] "GET /_resource/fs/img/close.png HTTP/1.0" 304 - "http://beta.polityka.pl/swiat/komentarze/1500387,1,po-unijnym-szczycie.read" "Opera/9.80 (Windows NT 5.1; U; pl) Presto/2.2.15 Version/10.00" 0/256
83.2.107.220 - - [31/Oct/2009:23:03:08 +0100] "GET /_resource/fs/res/img-blogi.png HTTP/1.0" 304 - "http://beta.polityka.pl/swiat/komentarze/1500387,1,po-unijnym-szczycie.read" "Opera/9.80 (Windows NT 5.1; U; pl) Presto/2.2.15 Version/10.00" 0/229
83.2.107.220 - - [31/Oct/2009:23:03:08 +0100] "GET /_resource/fs/img/presseurop.png HTTP/1.0" 304 - "http://beta.polityka.pl/swiat/komentarze/1500387,1,po-unijnym-szczycie.read" "Opera/9.80 (Windows NT 5.1; U; pl) Presto/2.2.15 Version/10.00" 0/251
83.2.107.220 - - [31/Oct/2009:23:03:08 +0100] "GET /_resource/fs/img/polityka_logo_small.png HTTP/1.0" 304 - "http://beta.polityka.pl/swiat/komentarze/1500387,1,po-unijnym-szczycie.read" "Opera/9.80 (Windows NT 5.1; U; pl) Presto/2.2.15 Version/10.00" 0/222
83.2.107.220 - - [31/Oct/2009:23:03:08 +0100] "GET /_resource/fs/img/ajax_loading_small.gif HTTP/1.0" 304 - "http://beta.polityka.pl/swiat/komentarze/1500387,1,po-unijnym-szczycie.read" "Opera/9.80 (Windows NT 5.1; U; pl) Presto/2.2.15 Version/10.00" 0/220


Jest to bardzo cenne źródło żądań wysyłanych do aplikacji, z których stworzę scenariusz testowy. Wydobywam więc wszystkie żądania z godziny 9tej i zapisuję je w pliku /tmp/requests.txt


cat /var/log/apache2/access.log | grep 31/Oct/2009:09 | awk '{print $7}' > /tmp/requests.txt


I już jestem w ogródku. Czas przywitać się z gąską...
Pozostało jakoś z pliku o strukturze:

/swiat/komentarze/_resource/fs/img/ajax_loading_small.gif
/swiat/komentarze/_resource/fs/res/img-blogi.png
/swiat/komentarze/_resource/res/20000457/cust/f120x80

wykonać żądania do poszczególnych zasobów.

Tu z pomocą przychodzi program Apache Benchmark i kilka poleceń linuksowych.

Odpalam więc scenariusz poleceniem:
for i in $(cat /tmp/requests.txt);do ab -n 2 -c 2 "http://nazwahosta$i"; done

-n 2 odpali każde żądanie 2 razy
-c 2 zrobi to w 2 wątkach

Dobre efekty daje odpalenie kilku scenariuszy testowych, np. z godziny 8, 9, 10, 11, zrównoleglonych screenem, czyli
screen for i in $(cat /tmp/requests.txt);do ab -n 2 -c 2 "http://nazwahosta$i"; done

A jakie są twoje patenty, na testy wydajnościowe?

sobota, 15 sierpnia 2009

Słabe referencje... z czym to się je?

Dzisiaj święto Wojska Polskiego. Parady wojskowej nie będzie z powodu kryzysu, ale sklepy (z zupełnie zresztą innego powodu) pozamykane. A jeść trzeba...

Może skuszę się na słabą referencję... ale z czym to się je?

Referencja jak referencja, spotkał się z nią każdy:

String string = new String("123");

Słaba referencja wygląda trochę inaczej

Reference<String> refString = new WeakReference<String>(new String("123"));

a dostęp do obiektu, na który referencja wskazuje uzyskujemy za pomocą wywołania metody get()

refString,get();

Nie o wygląd jednak tutaj chodzi.

Dopóki obiekt w naszym programie wskazywany jest przez zwykłą referencję, nie zostanie on usunięty z pamięci przez jakże uwielbiany przez wszystkich Garbage Collector.

Natomiast obiekt wskazywany przez słabą referencję, może być usunięty przez GC jeżeli zacznie brakować pamięci, mimo, że referencja do niego cały czas jest obecna w programie. Metoda get() zwróci wtedy wartość null.

Nie wierzysz?

Oto krótki program obrazujący działanie słabych (i mocnych) referencji:

package pl.matt;

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;


public class WeakReferenceTest {

int[] tab = new int[1024];

public static void main(String[] args) {
// zwykła referencja
String string = new String("123");

// słaba referencja
Reference<String> refString = new WeakReference<String>(new String("456"));

System.out.println(string);
System.out.println(refString.get());

java.util.List<WeakReferenceTest> list = new ArrayList<WeakReferenceTest>();

for (int i = 0; i < 1000000; i++) {
WeakReferenceTest test = new WeakReferenceTest();
list.add(test);
if (refString.get() == null) {
System.out.println(string);
System.out.println(i + " " + refString.get());
break;
}
}
}

}

Program w pętli przydziela pamięć tworząc nowe obiekty klasy WeakReferenceTest(). W pewnym momencie zaczyna się uruchamiać Garbage Collector i usuwa z pamięci obiekt który wskazywała słaba referencja.

Oprócz słabych referencji, w javie mamy jeszcze "średnio mocne" referencje SoftReference. Zasada ich działania jest podobna, z tym, że obiekty przez nie wskazywane żyją dłużej.

Obiekt wskazywany przez miękką referencję zostanie usunięty z pamięci tuż przed rzuceniem wyjątku java.lang.OutOfMemoryError.

Doskonałe narzędzia, to implementacji mechanizmów pamięci podręcznej.

Żeby dopełnić obrazu całości... są jeszcze referencje fantomowe PhantomReference. Obiekty wskazywane przez te referencje są usuwane przy każdym uruchomieniu GC, o ile nie wskazuje na nie żadna inna referencja. Można je stosować, aby mieć informację o tym, kiedy dany obiekt został usunięty z pamięci.

Aż się głodny zrobiłem, czas chyba zjeść coś bardziej konkretnego.

PS. Czego używacie do przeglądania RSSów? Ja jak dotąd Thunderbirda, ale ciągle szukam czegoś lepszego.

sobota, 1 sierpnia 2009

Seam z EhCache

W poprzednim wpisie wygenerowaliśmy sobie pierwszą aplikację Seamową przy pomocy wtyczki JBoss Tools.

Sposób bardzo fajny do szybkiej prezentacji, ale nie oszukujmy się, jest to sposób dobry dla dziewczyn. Do poważniejszych rozwiązań się nie nadaje.
Głównym minusem jest brak skryptu budującego aplikację, czy to antowego, czy to mavenowego.

Dlatego w dzisiejszym wpisie wygenerujemy sobie prostą aplikację Seamową, przy pomocy narzędzia seam-gen. Kilka dni temu światłu dziennemu ukazał się Seam 2.2.0.GA, ale ja posłużę się wersją 2.1.1.GA, głównie dlatego, że mam już ją zainstalowaną.

Wchodzę zatem do katalogu, w którym zainstalowałem Seama i konfiguruję go wykonując polecenie
./seam setup

mateusz@mateusz-laptop:/opt/jboss-seam-2.1.1.GA$ ./seam setup
SEAM_HOME: /opt/jboss-seam-2.1.1.GA
Using seam-gen sources from: /opt/jboss-seam-2.1.1.GA/seam-gen
Buildfile: /opt/jboss-seam-2.1.1.GA/seam-gen/build.xml

init:

setup:
[echo] Welcome to seam-gen :-)
[input] Enter your Java project workspace (the directory that contains your Seam projects) [/home/mateusz/priv/workspace/] [/home/mateusz/priv/workspace/]

[input] Enter your JBoss AS home directory [/opt/jboss-5.0.1.GA/] [/opt/jboss-5.0.1.GA/]

[input] Enter the project name [seam_test] [seam_test]

[echo] Accepted project name as: seam_test
[input] Do you want to use ICEfaces instead of RichFaces [n] (y, [n])

[input] skipping input as property icefaces.home.new has already been set.
[input] Select a RichFaces skin [ruby] (blueSky, classic, deepMarine, DEFAULT, emeraldTown, japanCherry, [ruby], wine)

[input] Is this project deployed as an EAR (with EJB components) or a WAR (with no EJB support) [ear] ([ear], war)

[input] Enter the Java package name for your session beans [pl.matt.session] [pl.matt.session]

[input] Enter the Java package name for your entity beans [pl.matt.model] [pl.matt.model]

[input] Enter the Java package name for your test cases [pl.matt.test] [pl.matt.test]

[input] What kind of database are you using? [mysql] (hsql, [mysql], oracle, postgres, mssql, db2, sybase, enterprisedb, h2)

[input] Enter the Hibernate dialect for your database [org.hibernate.dialect.MySQLInnoDBDialect] [org.hibernate.dialect.MySQLInnoDBDialect]

[input] Enter the filesystem path to the JDBC driver jar [/opt/jboss-seam-2.1.1.GA/lib/hsqldb.jar] [/opt/jboss-seam-2.1.1.GA/lib/hsqldb.jar]
/home/mateusz/java/lib/mysql-connector-java-5.1.7-bin.jar
[input] Enter JDBC driver class for your database [com.mysql.jdbc.Driver] [com.mysql.jdbc.Driver]

[input] Enter the JDBC URL for your database [jdbc:mysql:///test] [jdbc:mysql:///test]

[input] Enter database username [xxx] [xxx]

[input] Enter database password [xxx] [xxx]

[input] skipping input as property hibernate.default_schema.entered has already been set.
[input] Enter the database catalog name (it is OK to leave this blank) [] []

[input] Are you working with tables that already exist in the database? [n] (y, [n])

[input] Do you want to drop and recreate the database tables and data in import.sql each time you deploy? [n] (y, [n])

[delete] Deleting: /opt/jboss-seam-2.1.1.GA/seam-gen/build.properties
[propertyfile] Creating new property file: /opt/jboss-seam-2.1.1.GA/seam-gen/build.properties
[echo] Installing JDBC driver jar to JBoss AS
[echo] Type './seam create-project' to create the new project


podaję ścieżki do przestrzeni roboczej, do serwera JBoss, parametry bazy danych, nazwy pakietów i inne ustawienia aplikacji.

następnie tworzę projekt poleceniem
./seam create-project


mateusz@mateusz-laptop:/opt/jboss-seam-2.1.1.GA$ ./seam create-project
SEAM_HOME: /opt/jboss-seam-2.1.1.GA
Using seam-gen sources from: /opt/jboss-seam-2.1.1.GA/seam-gen
Buildfile: /opt/jboss-seam-2.1.1.GA/seam-gen/build.xml

init:

init-properties:
[echo] /opt/jboss-5.0.1.GA/

validate-workspace:

validate-project:

icefaces-staging-copy:

initcopy:

initpoms:
[echo] Setting up dependencies
[copy] Copying 1 file to /opt/jboss-seam-2.1.1.GA/classes/poms
[artifact:install] [INFO] Installing /opt/jboss-seam-2.1.1.GA/classes/poms/root.pom to /home/mateusz/.m2/repository/org/jboss/seam/root/2.1.1.GA/root-2.1.1.GA.pom
[copy] Copying 1 file to /opt/jboss-seam-2.1.1.GA/classes/poms
[artifact:install] [INFO] Installing /opt/jboss-seam-2.1.1.GA/classes/poms/parent.pom to /home/mateusz/.m2/repository/org/jboss/seam/parent/2.1.1.GA/parent-2.1.1.GA.pom
[copy] Copying 1 file to /opt/jboss-seam-2.1.1.GA/classes/poms
[copy] Copying 1 file to /opt/jboss-seam-2.1.1.GA/classes/poms
[copy] Copying 1 file to /opt/jboss-seam-2.1.1.GA/classes/poms
[copy] Copying 1 file to /opt/jboss-seam-2.1.1.GA/classes/poms
[copy] Copying 1 file to /opt/jboss-seam-2.1.1.GA/classes/poms
[copy] Copying 1 file to /opt/jboss-seam-2.1.1.GA/classes/poms
[copy] Copying 1 file to /opt/jboss-seam-2.1.1.GA/classes/poms
[copy] Copying 1 file to /opt/jboss-seam-2.1.1.GA/classes/poms
[copy] Copying 1 file to /opt/jboss-seam-2.1.1.GA/classes/poms
[copy] Copying 1 file to /opt/jboss-seam-2.1.1.GA/classes/poms
[copy] Copying 1 file to /opt/jboss-seam-2.1.1.GA/classes/poms
[copy] Copying 1 file to /opt/jboss-seam-2.1.1.GA/classes/poms
[copy] Copying 1 file to /opt/jboss-seam-2.1.1.GA/classes/poms
[copy] Copying 1 file to /opt/jboss-seam-2.1.1.GA/classes/poms
[copy] Copying 1 file to /opt/jboss-seam-2.1.1.GA/classes/poms

copyseam:

copyseamdependencies:

copyjbossembedded:

copy-icefaces-home:

copy-icefaces-maven:

copy-lib:
[echo] Copying Seam and dependencies to the /home/mateusz/priv/workspace//seam_test/lib directory...
[copy] Copying 121 files to /home/mateusz/priv/workspace/seam_test/lib
[copy] Copied 5 empty directories to 2 empty directories under /home/mateusz/priv/workspace/seam_test/lib
[echo] Copying JBoss Embedded configuration to the /home/mateusz/priv/workspace//seam_test/bootstrap directory...
[copy] Copying 30 files to /home/mateusz/priv/workspace/seam_test/bootstrap

file-copy-war:

file-copy-ear:
[echo] Copying resources needed for EAR deployment to the /home/mateusz/priv/workspace//seam_test/resources directory...
[copy] Copying 1 file to /home/mateusz/priv/workspace/seam_test/resources/WEB-INF
[copy] Copying 1 file to /home/mateusz/priv/workspace/seam_test
[copy] Copying 1 file to /home/mateusz/priv/workspace/seam_test
[copy] Copying 1 file to /home/mateusz/priv/workspace/seam_test
[copy] Copying 1 file to /home/mateusz/priv/workspace/seam_test
[copy] Copying 7 files to /home/mateusz/priv/workspace/seam_test/resources

setup-filters:

file-copy:
[copy] Copying 1 file to /home/mateusz/priv/workspace/seam_test
[copy] Copying 3 files to /home/mateusz/priv/workspace/seam_test/resources
[copy] Copying 11 files to /home/mateusz/priv/workspace/seam_test/resources
[copy] Copying 4 files to /home/mateusz/priv/workspace/seam_test
[copy] Copying 1 file to /home/mateusz/priv/workspace/seam_test
[copy] Copying 1 file to /home/mateusz/priv/workspace/seam_test
[copy] Copying 1 file to /home/mateusz/priv/workspace/seam_test
[copy] Copying 1 file to /home/mateusz/priv/workspace/seam_test/.settings
[copy] Copying 1 file to /home/mateusz/priv/workspace/seam_test
[copy] Copying 1 file to /home/mateusz/priv/workspace/seam_test
[mkdir] Created dir: /home/mateusz/priv/workspace/seam_test/nbproject
[copy] Copying 3 files to /home/mateusz/priv/workspace/seam_test/nbproject
[copy] Copying 1 file to /home/mateusz/priv/workspace/seam_test/resources
[copy] Copying 1 file to /home/mateusz/priv/workspace/seam_test/resources
[copy] Copying 1 file to /home/mateusz/priv/workspace/seam_test/resources
[copy] Copying 1 file to /home/mateusz/priv/workspace/seam_test/resources
[copy] Copying 1 file to /home/mateusz/priv/workspace/seam_test/resources
[copy] Copying 1 file to /home/mateusz/priv/workspace/seam_test/resources
[copy] Copying 12 files to /home/mateusz/priv/workspace/seam_test/view
[copy] Copying 9 files to /home/mateusz/priv/workspace/seam_test/view
[copy] Copying 1 file to /home/mateusz/priv/workspace/seam_test/src/hot/pl/matt/session
[copy] Copying 3 files to /home/mateusz/priv/workspace/seam_test
[mkdir] Created dir: /home/mateusz/priv/workspace/seam_test/src/main/pl/matt/model
[mkdir] Created dir: /home/mateusz/priv/workspace/seam_test/src/test/pl/matt/test
[copy] Copying 1 file to /home/mateusz/priv/workspace/seam_test/src/test

create-project:
[echo] A new Seam project named 'seam_test' was created in the /home/mateusz/priv/workspace/ directory
[echo] Type './seam explode' and go to http://localhost:8080/seam_test
[echo] Eclipse Users: Import the project using File > Import... > Existing Projects into Workspace, set the root directory to /home/mateusz/priv/workspace/, then select the project named seam_test
[echo] NetBeans Users: Open the project using File > Open project... and select the project folder /home/mateusz/priv/workspace//seam_test
[echo] IDEA Users: Open the project using File > Open project... and select the file /home/mateusz/priv/workspace//seam_test/seam_test.ipr

BUILD SUCCESSFUL


i zgodnie z instrukcją

Eclipse Users: Import the project using File > Import... > Existing Projects into Workspace, set the root directory to /home/mateusz/priv/workspace/, then select the project named seam_test

importuję projekt do Eclipsa.

W pakiecie pl.matt.model tworzę klasę Employee

package pl.matt.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import org.hibernate.validator.Email;
import org.hibernate.validator.Length;
import org.hibernate.validator.Max;
import org.hibernate.validator.Min;
import org.hibernate.validator.NotNull;

@Entity
@Table(name="employees")
public class Employee {

@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;

@Length(max=128)
@NotNull
private String name;

@Column(length=128)
@NotNull
private String surname;

@Email
@NotNull
@Column(length=128)
private String email;

@Min(0)
@Max(130)
@NotNull
private int age;

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 String getSurname() {
return surname;
}

public void setSurname(String sureName) {
this.surname = sureName;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

}


i generuję do niej prostego CRUDa poleceniem
./seam generate-ui

mateusz@mateusz-laptop:/opt/jboss-seam-2.1.1.GA$ ./seam generate-ui
SEAM_HOME: /opt/jboss-seam-2.1.1.GA
Using seam-gen sources from: /opt/jboss-seam-2.1.1.GA/seam-gen
Buildfile: /opt/jboss-seam-2.1.1.GA/seam-gen/build.xml

init:

init-properties:
[echo] /opt/jboss-5.0.1.GA/

validate-workspace:

validate-project:

init-generate:

generate-ui:
[echo] Building project 'seam_test' to generate views and controllers

init:

groovy.compile:

groovy.copy:

compile:

copyclasses:

jar:
[copy] Copying 1 file to /home/mateusz/priv/workspace/seam_test/exploded-archives/seam_test.ear/seam_test.jar/META-INF
[copy] Copying 1 file to /home/mateusz/priv/workspace/seam_test/exploded-archives/seam_test.ear/seam_test.jar
[hibernate] Executing Hibernate Tool with a JPA Configuration
[hibernate] 1. task: generic exportertemplate: view/list.xhtml.ftl
[hibernate] 2009-08-01 18:56:21 org.hibernate.cfg.annotations.Version
[hibernate] INFO: Hibernate Annotations 3.3.0.GA
[hibernate] 2009-08-01 18:56:21 org.hibernate.cfg.Environment
[hibernate] INFO: Hibernate 3.2.4.sp1
[hibernate] 2009-08-01 18:56:21 org.hibernate.cfg.Environment
[hibernate] INFO: hibernate.properties not found
[hibernate] 2009-08-01 18:56:21 org.hibernate.cfg.Environment buildBytecodeProvider
[hibernate] INFO: Bytecode provider name : cglib
[hibernate] 2009-08-01 18:56:21 org.hibernate.cfg.Environment
[hibernate] INFO: using JDK 1.4 java.sql.Timestamp handling
[hibernate] 2009-08-01 18:56:21 org.hibernate.ejb.Version
[hibernate] INFO: Hibernate EntityManager 3.3.1.GA
[hibernate] 2009-08-01 18:56:22 org.hibernate.cfg.AnnotationBinder bindClass
[hibernate] INFO: Binding entity from annotated class: pl.matt.model.Employee
[hibernate] 2009-08-01 18:56:22 org.hibernate.cfg.annotations.EntityBinder bindTable
[hibernate] INFO: Bind entity pl.matt.model.Employee on table employees
[hibernate] 2009-08-01 18:56:22 org.hibernate.validator.Version
[hibernate] INFO: Hibernate Validator 3.0.0.GA
[hibernate] 2009-08-01 18:56:22 org.hibernate.tool.Version
[hibernate] INFO: Hibernate Tools 3.2.2.GA
[hibernate] 2. task: generic exportertemplate: view/view.xhtml.ftl
[hibernate] 3. task: generic exportertemplate: view/view.page.xml.ftl
[hibernate] 4. task: generic exportertemplate: view/edit.xhtml.ftl
[hibernate] 5. task: generic exportertemplate: view/edit.page.xml.ftl
[hibernate] 6. task: generic exportertemplate: src/EntityList.java.ftl
[hibernate] 7. task: generic exportertemplate: view/list.page.xml.ftl
[hibernate] 8. task: generic exportertemplate: src/EntityHome.java.ftl
[hibernate] 9. task: generic exportertemplate: view/layout/menu.xhtml.ftl
[javaformatter] Java formatting of 3 files completed. Skipped 0 file(s).
[echo] Type './seam restart' and go to http://localhost:8080/seam_test

BUILD SUCCESSFUL
Total time: 4 seconds



Uruchamiam JBossa i wchodzę na adres:
http://localhost:8080/seam_test/
moim oczom ukazuje się znzna już aplikacja


Dodaję zatem jednego użytkownika


i otwieram w trzech zakładkach. Na konsoli JBossa obserwuję zapytania SQL jakie wykonuje aplikacja:

INFO [STDOUT] Hibernate:
select
employee0_.id as id2_0_,
employee0_.age as age2_0_,
employee0_.email as email2_0_,
employee0_.name as name2_0_,
employee0_.surname as surname2_0_
from
employees employee0_
where
employee0_.id=?
INFO [STDOUT] Hibernate:
select
employee0_.id as id2_0_,
employee0_.age as age2_0_,
employee0_.email as email2_0_,
employee0_.name as name2_0_,
employee0_.surname as surname2_0_
from
employees employee0_
where
employee0_.id=?
INFO [STDOUT] Hibernate:
select
employee0_.id as id2_0_,
employee0_.age as age2_0_,
employee0_.email as email2_0_,
employee0_.name as name2_0_,
employee0_.surname as surname2_0_
from
employees employee0_
where
employee0_.id=?


Jak widać, trzy razy tem sam SELECT.

Zakładając, że tylko nasza aplikacja kożysta z bazy danych test, możemy się pokusić, o włączenie pamięci podręcznej drugiego poziomu, która umożliwi wyrugowanie powtarzających się zapytań.

Dostawców pamięci drugiego poziomu jest kilku. Ja się zdecyduję na najbardziej chyba popularny EhCache.

Uzupełniam zatem spis plików jar, znajdujący się w pliku
deployed-jars-ear.list, które chcę aby znalazły się w aplikacji ear o wpisy:

commons-lang.jar
commons-collections.jar
commons-beanutils.jar
richfaces-ui.jar
commons-digester.jar
commons-beanutils.jar
commons-lang.jar
hibernate.jar
hibernate-all.jar
hibernate-annotations.jar
hibernate-commons-annotations.jar
hibernate-entitymanager.jar
hibernate-validator.jar
cglib.jar
asm.jar
ehcache.jar


w pliku
persistence-dev.xml
dodaję wpisy


<property name="hibernate.cache.use_second_level_cache" value="true" />
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.EhCacheProvider" />


Cały plik wygląda następująco:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Persistence deployment descriptor for dev profile -->
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">

<persistence-unit name="seam_test">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:/seam_testDatasource</jta-data-source>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLInnoDBDialect" />
<property name="hibernate.hbm2ddl.auto" value="update" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="true" />
<property name="jboss.entity.manager.factory.jndi.name"
value="java:/seam_testEntityManagerFactory" />
<property name="hibernate.cache.use_second_level_cache"
value="true" />
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.EhCacheProvider" />
</properties>
</persistence-unit>

</persistence>

natomiast klasę Employee dekoruję adnotacją

@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)


i restartuję aplikację.
Start serwera kończy się komunikatem błędu:


ERROR [AbstractKernelController] Error installing to Start: name=persistence.unit:unitName=seam_test.ear/seam_test.jar#seam_test state=Create
java.lang.IllegalArgumentException: Cache name cannot contain '/' characters.
at net.sf.ehcache.Cache.setName(Cache.java:1272)
at net.sf.ehcache.CacheManager.addCache(CacheManager.java:501)
at org.hibernate.cache.EhCacheProvider.buildCache(EhCacheProvider.java:87)
at org.hibernate.cache.CacheFactory.createCache(CacheFactory.java:61)
at org.hibernate.impl.SessionFactoryImpl.(SessionFactoryImpl.java:214)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1294)
at org.hibernate.cfg.AnnotationConfiguration.buildSessionFactory(AnnotationConfiguration.java:915)
at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:730)
at org.hibernate.ejb.HibernatePersistence.createContainerEntityManagerFactory(HibernatePersistence.java:127)
at org.jboss.jpa.deployment.PersistenceUnitDeployment.start(PersistenceUnitDeployment.java:301)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.jboss.reflect.plugins.introspection.ReflectionUtils.invoke(ReflectionUtils.java:59)
at org.jboss.reflect.plugins.introspection.ReflectMethodInfoImpl.invoke(ReflectMethodInfoImpl.java:150)
at org.jboss.joinpoint.plugins.BasicMethodJoinPoint.dispatch(BasicMethodJoinPoint.java:66)
at org.jboss.kernel.plugins.dependency.KernelControllerContextAction$JoinpointDispatchWrapper.execute(KernelControllerContextAction.java:241)
at org.jboss.kernel.plugins.dependency.ExecutionWrapper.execute(ExecutionWrapper.java:47)
at org.jboss.kernel.plugins.dependency.KernelControllerContextAction.dispatchExecutionWrapper(KernelControllerContextAction.java:109)
at org.jboss.kernel.plugins.dependency.KernelControllerContextAction.dispatchJoinPoint(KernelControllerContextAction.java:70)
at org.jboss.kernel.plugins.dependency.LifecycleAction.installActionInternal(LifecycleAction.java:221)
at org.jboss.kernel.plugins.dependency.InstallsAwareAction.installAction(InstallsAwareAction.java:54)
at org.jboss.kernel.plugins.dependency.InstallsAwareAction.installAction(InstallsAwareAction.java:42)
at org.jboss.dependency.plugins.action.SimpleControllerContextAction.simpleInstallAction(SimpleControllerContextAction.java:62)
at org.jboss.dependency.plugins.action.AccessControllerContextAction.install(AccessControllerContextAction.java:71)
at org.jboss.dependency.plugins.AbstractControllerContextActions.install(AbstractControllerContextActions.java:51)
at org.jboss.dependency.plugins.AbstractControllerContext.install(AbstractControllerContext.java:348)
at org.jboss.dependency.plugins.AbstractController.install(AbstractController.java:1598)
at org.jboss.dependency.plugins.AbstractController.incrementState(AbstractController.java:934)
at org.jboss.dependency.plugins.AbstractController.resolveContexts(AbstractController.java:1062)
at org.jboss.dependency.plugins.AbstractController.resolveContexts(AbstractController.java:984)
at org.jboss.dependency.plugins.AbstractController.install(AbstractController.java:774)
at org.jboss.dependency.plugins.AbstractController.install(AbstractController.java:540)
at org.jboss.deployers.vfs.deployer.kernel.BeanMetaDataDeployer.deploy(BeanMetaDataDeployer.java:121)
at org.jboss.deployers.vfs.deployer.kernel.BeanMetaDataDeployer.deploy(BeanMetaDataDeployer.java:51)
at org.jboss.deployers.spi.deployer.helpers.AbstractSimpleRealDeployer.internalDeploy(AbstractSimpleRealDeployer.java:62)
at org.jboss.deployers.spi.deployer.helpers.AbstractRealDeployer.deploy(AbstractRealDeployer.java:50)
at org.jboss.deployers.plugins.deployers.DeployerWrapper.deploy(DeployerWrapper.java:171)
at org.jboss.deployers.plugins.deployers.DeployersImpl.doDeploy(DeployersImpl.java:1439)
at org.jboss.deployers.plugins.deployers.DeployersImpl.doInstallParentFirst(DeployersImpl.java:1157)
at org.jboss.deployers.plugins.deployers.DeployersImpl.doInstallParentFirst(DeployersImpl.java:1178)
at org.jboss.deployers.plugins.deployers.DeployersImpl.doInstallParentFirst(DeployersImpl.java:1210)
at org.jboss.deployers.plugins.deployers.DeployersImpl.install(DeployersImpl.java:1098)
at org.jboss.dependency.plugins.AbstractControllerContext.install(AbstractControllerContext.java:348)
at org.jboss.dependency.plugins.AbstractController.install(AbstractController.java:1598)
at org.jboss.dependency.plugins.AbstractController.incrementState(AbstractController.java:934)
at org.jboss.dependency.plugins.AbstractController.resolveContexts(AbstractController.java:1062)
at org.jboss.dependency.plugins.AbstractController.resolveContexts(AbstractController.java:984)
at org.jboss.dependency.plugins.AbstractController.change(AbstractController.java:822)
at org.jboss.dependency.plugins.AbstractController.change(AbstractController.java:553)
at org.jboss.deployers.plugins.deployers.DeployersImpl.process(DeployersImpl.java:781)
at org.jboss.deployers.plugins.main.MainDeployerImpl.process(MainDeployerImpl.java:698)
at org.jboss.system.server.profileservice.ProfileServiceBootstrap.loadProfile(ProfileServiceBootstrap.java:304)
at org.jboss.system.server.profileservice.ProfileServiceBootstrap.start(ProfileServiceBootstrap.java:205)
at org.jboss.bootstrap.AbstractServerImpl.start(AbstractServerImpl.java:405)
at org.jboss.Main.boot(Main.java:209)
at org.jboss.Main$1.run(Main.java:547)
at java.lang.Thread.run(Thread.java:619)


O co chodzi?

Otuż... chodz o błąd, który byłem tak uprzejmy i zgłosiłem.

JBoss wstawia slashe do nazw fragmentów pamięci podręcznej, a EhCache próbuje tworzyć pliki o nazwie takiej jak nazwa fragmentu pamięci podręcznej i ZONK. Co z tym fantem zrobić?

Napisać własny CacheProvider. Wbrew pozorom nie jest to takie trudne.
Tworzę więc klasę
JBoss5EhCacheProvider
która zamieni w nazwach pamięci podręcznej znaki '/' na '_'.


package pl.matt.cache;

import java.util.Properties;

import org.hibernate.cache.Cache;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.CacheProvider;

public class JBoss5EhCacheProvider implements CacheProvider {

private CacheProvider delegate = new org.hibernate.cache.EhCacheProvider();

@Override
public Cache buildCache(String name, Properties props) throws CacheException {
name = changeName(name);
return delegate.buildCache(name, props);
}

private String changeName(String name) {
if (name != null) {
return name.replace('/', '_');
}
return name;
}

@Override
public boolean isMinimalPutsEnabledByDefault() {
return delegate.isMinimalPutsEnabledByDefault();
}

@Override
public long nextTimestamp() {
return delegate.nextTimestamp();
}

@Override
public void start(Properties arg0) throws CacheException {
delegate.start(arg0);
}

@Override
public void stop() {
delegate.stop();
}

}



i ustawiam ją w pliku persistence-dev.xml jako CacjeProvider.

<property name="hibernate.cache.provider_class" value="pl.matt.cache.JBoss5EhCacheProvider" />


Czas na kolejny restart aplikacji. Chyba się udało...

[ServerImpl] JBoss (Microcontainer) [5.0.1.GA (build: SVNTag=JBoss_5_0_1_GA date=200902232048)] Started in 1m:4s:297ms


Otwieram kilka razy pracownika w różnych zakładkach.
W konsoli widzę:

[STDOUT] Hibernate:
select
employee0_.id as id0_,
employee0_.age as age0_,
employee0_.email as email0_,
employee0_.name as name0_,
employee0_.surname as surname0_
from
employees employee0_ limit ?

tylko raz. Kilka zapytań o obiekty, a do bazy zapytanie poszło jedno.
Sukces pełną gębą.