Archive for category Java

Logowanie zadawanych zapytań do bazy danych przy użyciu log4jdbc

Duża (jeżeli nie większość) część aplikacji korzysta z bazy danych. Zapisuje oraz pobiera z niej informacje praktycznie na okrągło i co ważniejsze, nie jest w stanie poprawnie funkcjonować bez niej. W przypadku Javy połączenia realizowane z bazą danych są opisane poprzez standard JDBC (Java DataBase Connectivity). Praktycznie wszystkie sterowniki do baz danych relacyjnych opierają się na tym standardzie i realizują opisane w nim funkcje. Ponieważ aplikacje tak silnie zależą od baz danych, często pojawiają się różne problemy związane z ich użyciem. W celu ich analizy przydatne może być możliwość sprawdzenia, jak wyglądają poszczególne zapytania do bazy danych wysłane przez aplikację, łącznie z informacje jakie dane są w nich przesyłane.

Jedną z możliwości sprawdzenia (dokładnego) jak wyglądają poszczególne zapytania do bazy danych, jest użycie specjalnego sterownika pośredniczącego pomiędzy naszą aplikacją a serownikiem bazodanowym. Pośrednik ten umożliwia zalogowanie odpowiedniego zapytania zanim zostanie ono przekazane do sterownika odpowiedzialnego za połączenie się z bazą danych i wykonywania na niej operacji. Takim sterownikiem pośredniczącym jest log4jdbc.

Instalacja biblioteki w aplikacji

Pierwszym krokiem powinno być pobranie odpowiedniej wersji biblioteki ze strony log4jdbc. Należy zwrócić uwagę, że inne wersje są zalecane w zależności od tego, jaka wersja Javy zostanie użyta do uruchomienia aplikacji. W moim przypadku najwłaściwszy był plik log4jdbc4-1.2beta1.jar.

Powyższy plik JAR należy dołączyć do ścieżki aplikacji. Ja w swoim przypadku korzystałem z aplikacji uruchomionej na serwerze aplikacyjnym JBoss, więc skopiowałem ten plik do katalogu [cc]common/lib[/cci]. Pozostał jeszcze tylko restart serwera i biblioteka jest gotowa do użycia.

Inicjalizacja połączenia

Istnieją dwa sposoby inicjalizacja biblioteki: w kodzie aplikacji poprzez wywołanie odpowiedniej klasy oraz poprzez odpowiednie utworzenie łańcucha połączenia z bazą danych w standardzie JDBC.

Inicjalizacja log4jdbc w kodzie aplikacji

Oto przykładowy kod źródłowy, jaki należy użyć w aplikacji :

1
2
3
4
5
6
7
// get connection from datasource
Connection conn = dataSource.getConnection();

// wrap the connection with log4jdbc
conn = new net.sf.log4jdbc.ConnectionSpy(conn);

// now use Connection as normal (but it will be audited by log4jdbc)

Czyli najpierw tworzymy lub pozyskujemy połączenie do bazy danych, a następnie przekazujemy je do instancji klasy ConnectionSpy, która od tej pory będzie pośredniczyć w wywoływaniu odpowiedniego kodu i logowaniu informacji przez bibliotekę log4jdbc.

Inicjalizacja połączenia przy użyciu JDBC

Drugim sposobem jest zdefiniowanie połączenia do bazy danych przy użyciu łańcucha JDBC. Robi się to dodając informację o użyciu biblioteki log4jdbc w łańcuchu definiującym połączenie. Jeżeli podstawowe połączenie z bazą danych wygląda następująco (połączenie z bazą danych MySQL):

jdbc:mysql://localhost:3306/baza_danych

To należy dodać do łańcucha ten ciąg znaków log4jdbc, tak aby wyglądał on następująco:

jdbc:log4jdbc:mysql://localhost:3306/baza_danych

Jako klasę sterownika należy użyć klasy: net.sf.log4jdbc.DriverSpy. Przykładowa konfiguracja źródła danych dla JBossa wygląda następująco:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<datasources>
  <local-tx-datasource>
    <jndi-name>bazaDanychDS</jndi-name>
    <connection-url>jdbc:log4jdbc:mysql://localhost:3306/baza_danych</connection-url>
    <driver-class>net.sf.log4jdbc.DriverSpy</driver-class>
    <!-- definicja użytkownika, hasła, innych parametrów potrzebnych do połączenia z bazą danych -->
  </local-tx-datasource>
</datasources>

Biblioteka log4jdbc automatycznie postara się zaincjalizować odpowiednią klasę sterownika dla używanej bazy danych. Pełna list domyślnie używanych klas (w zależności od wybranej bazy danych) znajduje się na stronie domowej tego projektu, poniżej znajduje się wybór kilku najczęściej używanych:

Baza danych Klasa sterownika
Oracle oracle.jdbc.driver.OracleDriver
MySQL com.mysql.jdbc.Driver
PostgresSQL org.postgresql.Driver
HSQLDB org.hsqldb.jdbcDriver

Jeżeli istnieje potrzeba użycia innego sterownika do bazy danych, można te informacje przekazać do biblioteki konfigurując odpowiednią zmienną systemową log4jdbc.drivers:

-Dlog4jdbc.drivers=<driverclass>[,</driverclass><driverclass>...]

Teraz pozostaje uruchomić aplikację i w logach powinny znaleźć się informacje o zadawanych zapytaniach, o wartościach poszczególnych parametrów w zapytaniu (szczególnie pomocne w podczas używania klasy PreparedStatement).

Konfiguracja loggerów

Biblioteka log4jdbc definiuje pięć różnych loggerów odpowiedzialnych za zapisywanie różnych informacji dotyczących komunikacji z bazą danych. Każdy z nich może mieć ustawiony jeden z poziomów logowania: DEBUG, INFO, ERROR, FATAL.

Jeżeli wszystkie będą ustawione na poziom FATAL, to faktycznie log4jdbc zostanie wyłączony. Wtedy też nowo tworzone połączenie nie będą otaczane klasą ConnectionSpy tylko będzie to bezpośrednie połączenie z bazą danych. Taki sposób działania jest przydatny, ponieważ można nawet w środowisku produkcyjnym mieć zdefiniowane połączenie z bazą danych przy użyciu log4jdbc, ale włączać je za pomocą definicji odpowiednich zmiennych, bez potrzeby modyfikowania łańcucha definiującego połączenie.

Znaczenie pozostałych poziomów logowania:

  • ERROR – wypisanie stack trace w logu w przypadku wystąpienia wyjątku SQLException
  • INFO – dodanie wyświetlania poleceń SQL
  • DEBUG – dodanie nazwy klasy oraz numeru linii w której zostało wykonane dane polecenie SQL (powoduje także większy narzut czasowy na zapisanie takie informacji w logu)

Dostępne loggery:

Logger Opis
jdbc.sqlonly logowanie samych poleceń SQL, w przypadku korzystania z klasy PreparedStatement także zostaną podane wartości poszczególnych parametrów
jdbc.sqltiming informacje o czasie wykonywania poszczególnych zapytań SQL
jdbc.audit logowanie wszystkich wywołań JDBC poza wynikami (używane do wyszukiwania problemów z JDBC)
jdbc.resultset logowanie wszystkich wywołań do klasy ResultSet
jdbc.connection logowanie informacji o otwieraniu i zamykaniu połączeń, pomocne w przypadku wystąpienia problemów z wyciekami połączeń

Przykładowa fragment linii poleceń wywołującej JVM w którym są te loggery włączane:

-Djdbc.sqlonly=DEBUG -Djdbc.sqltiming=DEBUG -Djdbc.audit=INFO -Djdbc.connection=INFO

Źródła

Tags: , , , , , , , ,

Odszukanie plików JAR z brakującymi definicjami klas

Często pojawiającym się problem (i bardzo irytującym przy okazji) podczas uruchamiania aplikacji napisanych w Javie jest brak jakiś klas potrzebnych do uruchomienia aplikacji. Ja się ostatnio spotkałem z takim problemem próbując testować jakąś aplikację, której autorów nikt od dawana nie widział.

Znam dwie metody na sprawdzenia, jak nazywa się plik JAR z daną klasą:

Odszukanie pliku JAR przy użyciu strony internetowej

Przy użyciu wyszukiwarki FindJAR.com można w łatwy sposób sprawdzić, w jakich plikach znajduje się poszukiwana klasa. Wystarczy w polu wyszukiwania wpisać nazwę klasy (niekoniecznie nawet z pełną nazwą pakietu), ewentualnie wybrać dokładną nazwę klasy z prezentowanych odpowiedzi i już widać, w jakich plikach znajduje się dana klasa.

Zrobiłem test dla klasy javax.jws.WebParam, wyniki można zobaczyć tutaj: http://www.findjar.com/class/javax/jws/WebParam.html:

findJAR wyniki dla WebParam

findJAR wyniki dla WebParam

Teraz pozostaje tylko pobrać plik i przynajmniej jedną klasę mamy już głowy ;).

Odszukanie pliku jar przy użyciu aplikacji

Do odszukania potrzebnych plików można także użyć aplikacji [cco]jarSearch[/cci]. W artykule Which jar contains my… można znaleźć dokładny opis możliwości aplikacji (większy niż przedstawiony tutaj) oraz link do źródeł.

Aplikacja przeszukuje podany katalog oraz podkatalogi w celu odnalezienia plików JAR i sprawdza czy znajduje się w nim podana klasa. Jeżeli znajdzie ją, to wyświetla odpowiednią informację.

Po pobraniu pliku zip jarSearchSrc.zip z plikami źródłowymi, należy je skompilować:

$ ant build
Buildfile: build.xml

build:
   [delete] Deleting directory /home/lukasz/install/java/jarSearch/classes
    [mkdir] Created dir: /home/lukasz/install/java/jarSearch/classes
    [javac] Compiling 4 source files to /home/lukasz/install/java/jarSearch/classes

BUILD SUCCESSFUL
Total time: 1 second

Aby użyć jarSearch należy z katalogu z plikiem build.xml wydać komendę:

$ java -cp jarSearch.jar com.isocra.utils.jarSearch.DirectorySearcher /jboss-as/client/ javax.jws.WebParam.class
/jboss-as/client/jbossws-native-jaxws.jar: javax/jws/WebParam.class
1 entries found.
Directory searcher: http://www.isocra.com

Pierwszym parametrem jest katalog z który zawiera pliki jar, drugim natomiast nazwa szukanej klasy. W powyższym przykładzie widać, że został odnaleziony jeden plik, który zawiera poszukiwaną klasę jbossws-native-jaxws.jar

Widać, że efekty działania obu sposobów są trochę inne. Strona findJAR oferuje nam możliwość odnalezienia wybranej klasy i następnie ściągnięcia odpowiedniego pliku. Natomiast aplikacja jarSearch pozwala po prostu sprawdzić w której bibliotece ukrywa się szukana klasa.

Źródła

Tags: , , , , ,

Zarządzanie transakcjami przez serwer aplikacji

W środowiskach zarządzanych (czyli np w serwerze aplikacyjnym JBoss AS) to te systemu mogą zarządzać transakcjami, czyli rozpoczynać, kończyć je w odpowiednim momencie, zwalniając z tego obowiązku programistę. W aplikacji można zdefiniować, jak dany fragment kodu ma wpływać na transakcje. Jeżeli sposób zachowanie nie zostanie zdefiniowany przez metodę, to kontener rozpoczyna transakcję zanim rozpocznie się wykonywanie metody, i kończy ją tuż przed końcem jej wykonywania. Nie są dozwolone zagnieżdżone transakcje, każda metoda może zostać skojarzona z jedną transakcją.

Definicja rodzaju transakcji

Rodzaje transakcji można zdefiniować przy użyciu adnotacji TransactionAttribute oraz TransactionAttributeType.

Adnotacja TransactionAttribute może występować zarówno na poziomie deklaracji klasy jak i deklaracji metody. Jeżeli zostanie podana przy deklaracji klasy, oznacza to że dany poziom transakcji ma zostać użyty we wszystkich metodach danej klasy, w przypadku deklaracji na poziomie metody odnosi się tylko do niej. W przypadku gdy występuje w jednym jak i drugim miejscu, pod uwagę brana jest wartość występująca przy deklaracji metody.

Poniżej przykład deklarowania rodzajów transakcji, na poziomie klasy i metod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;

@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public class UserManager {
   
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public String getUserName() {
        return null;
    }
   
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void setUserName(String name) {
       
    }
}

Typy transakcji

Za pomocą adnotacji TransactionAttributeType można zdefiniować rodzaj transakcji, jaka ma obowiązywać w metodach. Występują następujące typy transakcji:

  • MANDATORY
    Konieczność wykonania metody w ramach istniejącej już transakcji. Jeżeli dla danego klienta istnieje już działająca transakcji, dana metoda zostanie w jej ramach wykonana. Jeżeli natomiast taka transakcji nie istnieje, to zostanie wyrzucony wyjątek TransactionRequiredException. Należy użyć tego atrybutu używać wtedy, gdy dana metoda musi zostać wykonana w ramach transakcji klienta.
  • REQUIRED
    Jeżeli klient działa w ramach transakcji i zostanie wywołana metoda oznaczona tym typem, to zostanie ona wykonana w ramach tej transakcji. Jeżeli natomiast klient nie działa w ramach transakcji, to zostanie rozpoczęta nowa, zanim metoda zostanie uruchomiona. Jest to także domyślne zachowanie, jeżeli nie zostanie ustawiony typ transakcji.
  • REQUIRES_NEW
    Jeżeli klient nie działa w ramach transakcji, to po prostu jest rozpoczynana nowa przed uruchomieniem metody. Natomiast jeżeli jest istnieje już działająca transakcja, to podejmowane są następujące kroki:

    1. Zawieszenie aktualnie działającej transakcji.
    2. Rozpoczęcie nowej transakcji do wykonania wybranej metody.
    3. Wywołanie i wykonanie metody.
    4. Po zakończeniu działania metody ponowne uruchomienie wcześniej zawieszonej transakcji.

    Typu tego należy używać w sytuacji, gdy istnieje potrzeba wykonywania danej metody zawsze w nowej transakcji.

  • SUPPORTS
    Jeżeli klient działa w ramach transakcji i wywoła daną metodę, to zostanie ona wykonana w ramach tej transakcji. Jeżeli natomiast transakcja nie istnieje, to nie zostanie ona także rozpoczęta przed wykonaniem wybranej metody.
  • NOT_SUPPORTED
    Jeżeli klient działa w ramach transakcji, to zostanie ona zatrzymana na czas wywołania wybranej metody, a po jej wykonaniu ponownie przywrócona. Jeżeli nie ma takiej transakcji, metoda zostanie po prostu wykonana, bez rozpoczynania nowej.

    Należy używać tego typu wtedy, gdy wybrana metoda nie potrzebuje transakcji do działania, a zależy nam na większej wydajności jej wykonywania (transakcje mają negatywny wpływ na szybkość wykonywania metod).

  • NEVER
    Jeżeli klient działa w ramach transakcji i wywoła metodą oznaczoną tym typem, zostanie wyrzucony wyjątek RemoteException. Natomiast jeżeli transakcja nie istnieje, to metoda zostanie wykonana bez rozpoczynania nowej transakcji.

Mała tabela, która podsumowuje powyższe:

Zachowanie kontenera w zależności od typu i stanu transakcji
Nazwa atrybutu Transakcja istnieje Brak transakcji
MANDATORY używa istniejącej wyrzuca wyjątek
REQUIRED używa istniejącej rozpoczyna nową
REQUIRES_NEW zawiesza istniejącą, rozpoczyna nową rozpoczyna nową
SUPPORTS używa istniejącej działa bez
NOT_SUPPORTED zawiesza istniejącą działa bez
NEVER wyrzuca wyjątek działa bez

Źródła

Tags: , , , , ,

Model nawigacji w JSF

Model nawigacyjny JSF pozwala na zdefiniowanie nawigacji pomiędzy stronami oraz na wykonywania dodatkowych akcji związanych z przechodzeniem pomiędzy stronami. W terminologii JSF nawigacją jest nazywany zestaw reguł, za pomocą których można stwierdzić jaka następna strona ma zostać wyświetlona po wybraniu jakieś przycisku bądź linku znajdującego się na stronie. Reguły te są zdefiniowane w pliku konfiguracyjnym faces-config.xml.

Jak zdefiniować regułę nawigacyjną?

Zdefiniowanie prostej nawigacji pomiędzy stronami sprowadza się do:

  • zdefiniowaniu odpowiednich reguł w pliku konfiguracyjnym faces-config.xml
  • odwołanie się do poprzez zdefiniowany ciąg znaków z poziomu przycisku lub linku

Przykładowy zapis w pliku konfiguracyjnym:

<navigation-rule>
    <from-view-id>/login.jsp</from-view-id>
    <navigation-case>
        <from-outcome>sukces</from-outcome>
        <to-view-id>/home.jsp</to-view-id>
    </navigation-case>
    <navigation-case>
        <from-outcome>błąd</from-outcome>
        <to-view-id>/login.jsp</to-view-id>
    </navigation-case>
</navigation-rule>

Aby wykorzystać podany zapis, należy w następujący sposób zbudować link lub przycisk (w pliku login.jsp):

<h:commandButton id="submit" action="sukces"
        value="Submit" />

W pliku konfiguracyjnym faces-config.xml definiujemy zasady nawigacji. Czyli:

  • navigation-rule – definicja reguły nawigacyjnej
  • from-view-id – informacja, jakiej strony dotyczą reguły
  • navigation-case – definicje wszystkich reguł, jakie mają mieć zastosowanie dla danej strony
  • from-outcome – definicja ciągu znakowego, który będzie użyty do rozpoznania danego przypadku nawigacyjnego, czyli przekirowania na podaną stronę, ciąg ten musi się „znaleźć” w atrybucie action, podany albo bezpośrednio, lub też zwrócony przez jakąś metodę
  • to-view-id – na jaką stronę przekierować przeglądarkę

Natomiast w pliku login.jsp definiujemy odpowiedni interfejs użytkownika (klikalny, czyli przycisk bądź link). Tam w jako parametr dla atrybutu action wprowadzamy zdefiniowany ciąg znaków, lub też wywołujemy jakąś metodę, która zwróci dany ciąg znaków (zdefiniowany w pliku konfiguracyjnym), co umożliwi odpowiednie przekierowanie w zależności od akcji.

I tak:

<h:commandButton id="submit" action="#{loginManager.login}"
        value="Submit" />

Metoda login zwraca ciąg znaków sukces w przypadku powodzenia podczas logowania. Nastąpi wtedy wykonanie reguły nawigacyjnej tak oznaczonej, i przekierowanie do strony ]home.jsp. Natomiast jeżeli uwierzytelnienie użytkownika nie powiedzie się, metoda ta zwraca ciąg znaków błąd, co spowoduje przekierowanie z powrotem do strony logowania.

Można także zdefiniować globalne przekierowania na daną stronę:

<navigation-rule>
    <from-view-id>*</from-view-id>
    <navigation-case>
        <from-outcome>loguj</from-outcome>
        <to-view-id>/login.jsp</to-view-id>
    </navigation-case>
</navigation-rule>

Czyli niezależnie na jakiej stronie zostanie użyty w ciąg znaków loguj w atrybucie action, to zawsze nastąpi przekierowanie na stronę logowania.

Co się dzieje po wybraniu linka w przeglądarce?

W momencie, gdy użytkownik kliknie na przycisk lub też link, dany komponent generuje odpowiednie zdarzenie, którym informuje o tym fakcie. Zdarzenie to jest obsługiwane domyślną implementację interfejsu ActionListener, który wywołuje odpowiednią metodę z akcją zdefiniowaną w komponencie. Podana metoda wykonuje się i zwraca ciąg znaków, który zostanie użyty do podjęcia decyzji o nawigacji.

Zwrócony ciąg znaków jest przekazywany do domyślnej implementacji NavigationHandler, która bazując na tej wartości sprawdza, jaka strona powinna zostać wyświetlona.

Jeżeli uda się odnaleźć odpowiednią regułę nawigacyjną, rozpocznie się faza tworzenia docelowej strony HTML.

Źródła

Tags: , , , ,

Zakres działania ziaren zarządzanych przez JSF

W JSF ziarno zarządzane (managed bean) może mieć jeden z czterech zakresów:

  • application
    Obiekt o takim czasie życia funkcjonuje przez cały okres życia aplikacji, czyli jest tworzony w momencie uruchamiania aplikacji i niszczony w momencie zatrzymywania jej działania.
  • session
    Ziarna oznaczone w ten sposób mają czas życie ograniczony do długości trwania sesji. Czyli są tworzone w momencie tworzenia sesji i niszczone razem z jej zakończeniem. Sesja może zostać zniszczona: ręcznie przez aplikację lub też jej czas życia się skończył. Dlatego też dany obiekt może brać udział w wielu zapytaniach wysyłanych do serwera (np. przechowywać stan koszyka w sklepie internetowych).
  • request
    Czas życia ziarna jest ograniczony do pojedynczego zapytania, czyli od momentu rozpoczęcia przetwarzania żądania wyświetlenia strony do momentu jej całkowitego utworzenia. Po wysłaniu strony do klienta obiekt jest niszczony.
  • none
    Brak zakresu działania, używany czasem w dla ziaren zarządzających innymi obiektami.

Przykładowa definicji ziarna w pliku faces-config.xml:

<managed-bean>
    <managed-bean-name>customer</managed-bean-name>
    <managed-bean-class>
        com.mycompany.mybeans.CustomerBean
    </managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
    <managed-property>
        <property-name>mailingAddress</property-name>
        <value>#{addressBean.mail}</value>
    </managed-property>
    <managed-property>
        <property-name>streetAddress</property-name>
        <value>#{addressBean.street}</value>
    </managed-property>
    <managed-property>
        <property-name>customerType</property-name>
        <value>New</value>
    </managed-property>
</managed-bean>

Powyższy przykład pokazuje w jaki sposób zdefiniować zarządzalne ziarno oraz jak je zainicjalizować początkowymi wartościami.

Tags: , , , , , , , ,