Posts Tagged baza danych

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: , , , , , , , ,

Zmiana wewnętrznej bazy danych JBossa (HSQLDB) na MySql

W domyślnej instalacji JBoss używa wewnętrznie bazy danych HSQLDB. Jest to baza danych napisana w 100% w Javie. Całkiem dobrze sprawdza się w prostych zastosowaniach jako baza danych wbudowana, ale bywają z nią problemy jeżeli nasze wymagania rosną.

W przypadku serwera JBoss baza ta posiada kilka zalet:

  • bardzo dobrze sprawdza się w środowisku deweloperskim
  • jest skonfigurowana do działania od razu po instalacji serwera, nie wymaga żadnej konfiguracji
  • prosta w użyciu

Ale jest także szereg wad, które uniemożliwiają jej używanie w systemie produkcyjnym, charakteryzującym się potrzebą dużej wydajności i niezawodności:

  • brak izolacji transakcji
  • problemy z wyciskania wątków czy socketów
  • występowanie błędów w przypadku błędnego zamknięcia bazy danych
  • problemy ze stabilnością w przepadku dużego obciążenia serwerów

Zaleca się, aby w systemach produkcyjnych zmienić bazę danych HSQLDB na inną, bardziej stabilną i o większych możliwościach. Zmiana konfiguracji po stronie serwera JBoss nie jest bardzo skomplikowana, należy wykonać kilka kroków. Poniższy opis odnosi się do bazy danych MySql, ale nie powinno sprawić wielkich trudności przerobienie go na dowolną inna bazę danych.

Krok 1: Instalacja serowników bazy danych

Pierwszym krokiem powinna być instalacja odpowiednich sterowników bazy danych. Należy skopiować je do katalogu $JBOSS_HOME/common/lib. W tym katalogu znajdują się biblioteki dostępne we wszystkich profilach, więc będzie można w każdym z nich ich używać.

W przypadku bazy danych MySql odpowiednie sterowniki można pobrać ze strony producenta: Download Connector/J. Pobrane archiwum należy rozkompresować i skopiować znajdujący się tam plik mysql-connector-java-*.jar do katalogu $JBOSS_HOME/common/lib lub też katalogu [lib] danego profilu.

Krok 2: Konfiguracja bazy danych

Kolejnym krokiem jest utworzenie bazy danych oraz odpowiedniego użytkownika. Należy połączyć się z bazą danych i wydać odpowiednie polecenia:

$ mysql -u root

Teraz wystarczy utworzyć bazę danych i odpowiedniego użytkownika:

1
2
CREATE DATABASE jboss_server;
GRANT ALL PRIVILEGES ON jboss_server.* TO jboss_user@'localhost' IDENTIFIED BY 'haslo';

Oczywiście nazwa bazy danych, użytkownika oraz hasło należy sobie odpowiednio dobrać. Jeżeli baza danych nie znajduje się na tym samym serwerze co serwer aplikacji, to także należy odpowiednio zmodyfikować ten wpis (czyli zmienić localhost na odpowiednią nazwę hosta).

Krok 3: Konfiguracja domyślnego źródła danych

Domyślna baza danych nazywa się DefaultDS i jej konfiguracja (dla wbudowanej bazy danych) znajduje się w pliku deploy/hsqldb-ds.xml dla danej konfiguracji serwera JBoss. Aby używać innej bazy danych, należy:

  • usunąć plik hsqldb-ds.xml z katalogu deploy (najlepiej przenieść do go innej katalogu)
  • utworzyć nową definicję domyślnej bazy danych specyficzną dla wybranego serwera bazodanowego

Warto pamiętać, że przykładowe konfiguracje źródeł danych dla różnych baz danych można znaleźć w katalogu $JBOSS_HOME/docs/examples/jca.

Czyli należy usunąć plik hsqldb-ds.xml a następnie utworzyć plik default-ds.xml dla bazy danych MySql:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<datasources>
  <local-tx-datasource>
    <jndi-name>DefaultDS</jndi-name>
    <connection-url>jdbc:mysql://localhost:3306/jboss_server</connection-url>
    <driver-class>com.mysql.jdbc.Driver</driver-class>
    <user-name>jboss_user</user-name>
    <password>haslo</password>
    <exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name>
    <valid-connection-checker-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLValidConnectionChecker</valid-connection-checker-class-name>
  </local-tx-datasource>
</datasources>

Krok 4: Konfiguracja JMS

W domyślnej konfiguracji JMS korzysta z wbudowanej bazy danych. Zakłada, że tą bazą danych jest HSQLDB. Konfiguracja dostępu znajduje się w pliku deploy/messaging/hsqldb-persistence-service.xml. Należy zamiast tego pliku przygotować konfigurację specyficzną dla bazy danych. Przykładowe konfiguracje można znaleźć w katalogu $JBOSS_HOME/docs/examples/jms.

W podanej konfiguracji wystarczy skopiować plik $JBOSS_HOME/docs/examples/jms/mysql-persistence-service.xml do katalogu deploy/messaging/ oraz usunąć plik hsqldb-persistence-service.xml.

Po tych operacjach wystarczy uruchomić ponownie serwer aplikacji JBoss i jeżeli nie wystąpiły żadne błędy, powinniśmy mieć już zdefiniowaną zewnętrzną bazę danych dla serwera.

Źródła

Tags: , , , , ,

Poziom izolacji transakcji w JDBC

Dostępny interfejs dostępu do baz danych w Javie (JDBC) udostępnia kilka różnych poziomów izolacji poszczególnych transakcji. Pozwala na określenie, jak bardzo poszczególne transakcje mają być oddzielone od siebie. Niezależnie od poziomu izolacji transakcji, operacje takie jak wstawianie (INSERT), usuwanie (DELETE) oraz modyfikowanie (UPDATE) rekordów zachowują się zawsze tak samo, jedynie zachowanie operacji pobierającej (SELECT) dane z tabeli może być rożne.

Poziom transakcji jest ustawiany w ramach pojedynczego połączenia z bazą danych. Można go ustawia się go przy użyciu metody Connection.setTransactionIsolation(). Im wyższy poziom izolacji transakcji tym generalnie mniejsza wydajność operacji na bazie danych oraz większe prawdopodobieństwo wystąpienia blokad w dostępie do bazy danych.

Anomalie występujące w transakcjach

Poniżej znajduje się zestawienie anomalii, jakie mogą wystąpić w transakcjach:

Anomalie występujące w transakcjach
Nazwa anomalii Przykład działania anomalii
Dirty Reads

Mamy z nią do czynienia wtedy, gdy następuje w danej transakcji A odczyt zmodyfikowanych w transakcji B danych, a transakcja ta (B) nie została jeszcze zatwierdzona.

Transakcja A rozpoczyna się, i wywołuje:

SELECT * FROM zamowienia

Transakcja B w tym samym czasie wykonuje:

UPDATE zamowienie
SET wartosc_zamowienia=100
WHERE nr_zamowienia=123

Istnieje możliwość, że w transakcji A zostanie odczytana nowa wartość pola wartosc_zamowienia, nawet jeżeli nie zostanie to zapytanie zatwierdzone.

Non-Repeatable Reads

Ze zdarzeniem takim mamy do czynienia wtedy, gdy zapytania A wykonane w różnych momentach jednej transakcji może zwrócić inne wyniki (to samo zapytanie nie daje tego samego rezultatu). Zdarzyć się tak może, gdy w trakcie działania transakcji dane zostaną zmodyfikowane przez inną operacje na bazie danych.

Transakcja A rozpoczyna się, i wywołuje:

SELECT * FROM zamowienia
WHERE nr_zamowienia=123

Transakcja B w tym samym czasie wykonuje:

UPDATE zamowienie
SET wartosc_zamowienia=100
WHERE nr_zamowienia=123;

COMMIT;

Wykonanie ponowne zapytania w transakcji A spowoduje, że zostanie tym razem zwrócony zmodyfikowany rekord.

Phantom Reads

Z taką sytuacją mamy do czynienia wtedy, gdy transakcja A odczytuje dane z bazy danych, w trakcie jej trwanie transakcja B umieści w bazie danych rekord, który spełnia warunki zapytania z transakcji A. Jeżeli teraz w transakcji ponownie zostanie wykonane zapytanie, nowy rekord także zostanie zwrócony, co powoduje niezgodność wyniku działania dwóch zapytań w ramach tej samej transakcji.

Transakcja A odczytuje dane:

SELECT * FROM zamowienia
WHERE nr_zamowienia > 123

Transakcja B wstawia nowy wiersz do tabeli:

INSERT INTO zamowienia
(nr_zamowienia, nazwa)
VALUES ('140', 'Babol złapany');

COMMIT;

Jeżeli teraz w transakcji A ponownie wykonamy zapytanie, to nowy rekord także zostanie pobrany.

Poziomy transakcji

Rozróżniane są następujące poziomy transakcji:

  • TRANSACTION_READ_UNCOMMITTED

    Brak izolacji. Wszelkie zmiany, także te które nie są zatwierdzone jeszcze, są widoczne dla wszystkich zapytań w bazie danych.

  • TRANSACTION_READ_COMMITTED

    Minimalna izolacja, transakcje widzą tylko takie dane, które już są zatwierdzone i zapisane w bazie danych.

  • TRANSACTION_REPEATABLE_READ

    Poziom ten zapewnia powtarzalność odczytu danych. Jeżeli jakiś rekord zostanie odczytany, to nawet jego zatwierdzona modyfikacja w innej transakcji nie zmieni jego wartości przy ponownym odczycie.

  • TRANSACTION_SERIALIZABLE

    Najwyższy poziom transakcji, zwany szeregowym. Zapewnia on najlepszą izolację poszczególnych transakcji, które nie widzą swoich wzajemnych działań i nie wpływają na siebie. W momencie rozpoczęcia transakcji, stan bazy danych jest „zamrażany” i tylko na takim stanie transakcja może działać.

Lista anomalii jakie występują przy określonych typach transakcji, w zależności od sposobu blokowania danych:
Nazwa transakcji Blokada tabeli Blokada wiersza Wydajność
TRANSACTION_READ_UNCOMMITTED Możliwe: dirty reads, non-repeatable reads, phantom reads Możliwe: dirty reads, non-repeatable reads, phantom reads najszybsza
TRANSACTION_READ_COMMITTED Możliwe: non-repeatable reads i phantom reads Możliwe: non-repeatable reads i phantom reads szybka
TRANSACTION_REPEATABLE_READ Ponieważ blokowana jest cała tabela, to phantom reads nie są możliwe Możliwe są phantom reads średnia
TRANSACTION_SERIALIZABLE Brak anomalii Brak anomalii wolna

Źródła

Tags: , , , ,