poniedziałek, 25 sierpnia 2008

java.util.Date vs java.sql.Date

Natknąłem się na ciekawe rozwiązanie architektoniczne w JDBC: obiekt java.sql.Date. Jest to obiekt, który dziedziczy bezpośrednio po java.util.Date, pozwalający nam mapować w naszych beanch pola typu Date z naszych baz danych.
Czy wszystko jest takie piekne? Nie do końca, gdyż w naszych beanach zwykle trzymamy obiekty typu java.util.Date.
Wyboraźmy sobie prosty przykład:

Mamy klase User w postaci

public class User
{
protected String userName;
protected Date birthDate; // tutaj java.util.Date czyli standard

//odpowiednie gettery i settery
}
Posiadamy również odpowiadającą jej tabelę w bazie danych:

CREATE TABLE Users (
userName VARCHAR2(150),
birthDate DATE );

Sytuacja jak najbardziej standardowa. Teraz jeśli wyciągnięte dane z bazy (przy pomocy ResultSeta) dodamy do naszego beana:

User u = new User();
u.setUserName(rSet.getString(1));
u.setBirthDate(rSet.getDate(2));

nic się nie stanie. W prawdzie metoda User.setBirthDate oczekuję java.util.Date, a ResultSet.getDate zwraca java.sql.Date, to jednak wszystko jest w porządku, gdyż java.sql.Date dziedziczy po java.util.Date. Jaja zaczynają się, gdy informacje chcemy ponownie przekazać do bazy danych.
Teraz gdy updateujemy jakies informacje przy pomocy jakies procedury skladowanej czy poprzez wywolanie SQL'owego UPDATE, zawsze ale to zawsze poprosza nas o Date w postaci java.sql.Date. Czyli kod
statement.setDate(2,user.getBirthDate());
sie po prostu nie skompiluje. Rzutowanie nic tu nie da, gdyż przykładowo poniższy kod:
user.setBirthDate(new Date());
statement.setDate(2,(java.sql.Date)user.getBirthDate()); // tu exception
się poprostu wywali przy pierwszym wywołaniu. Trzeba utworzyć explicite obiekt java.sql.Date, ale żeby nie było za prosto, nie ma żadnego konstruktora typu public Date(java.util.Date utilDate). Nie, przecież to by było zbyt proste. Zamiast tego musimy zrobic new java.sql.Date(utilDate.getTime()). Konstruktor java.sql.Date przyjmuje longa jako liczbę milisekund od roku 1970, a metoda getTime z java.util.Date taka informacje zwraca. Wszystko działa okej, ale czy na pewno to jest dobre rozwiązanie? Szczerze mówiąc, ja nie wiem. Skoro sami ludzie z Sun Microsystems mówią, że java.sql.Date to tylko "thin wrapper", to po co w ogóle został wprowadzony? Czy istnieje jakiś lepszy (w sensie nie polegający na roku 1970) sposób na konwertowanie dat z java.util na java.sql? Googlałem pobieżnie, nie znalazłem. Może ktoś zna?

8 komentarzy:

koziołek pisze...

Chyba joda time library ma jakieś takie ładne metody opakowujące ten cały bałagan.
Co do obiektu java.util.Date to został on wycofany na korzyść Calendar. Jednak nie wszędzie i nie zawsze. Dlatego warto pracować z JPA, a nie z JDBC. Tam mapowania w większości przypadków odwala za nas framework.

Paweł Szulc pisze...

@koziolek: Jesli pracujesz z JPA to oczywiscie nie ma problemu. Ale nieprawda jest ze obiekt Date zostal deprecate na rzecz calendara. Deprecated sa kontruktory Date'a (poza defaultowym new Date()), natomiast do trzymania dat myślę, że obiekt Date jest wciąż jak najbardziej poprawny. Zresztą jak mapujesz w JPA jakas date to jakiego obiektu uzywasz? Raczej java.util.Date

pawelstawicki pisze...
Ten komentarz został usunięty przez autora.
pawelstawicki pisze...

Obiekt Date nie jest deprecated, ale nie jest zalecany. Dlatego też powstał Calendar. Podstawowym błędem projektantów javy było to, że zrobili Date mutable, to znaczy że można zmienić jego stan po utworzeniu. Spróbuj go teraz użyć jako klucza mapy ;)

Sam miałem kiedyś poważny, trudny do wyśledzenia problem właśnie tym spowodowany. Ustawiałem sobie Date jako klucz mapy, jako wartość cośtam, potem Date się zmieniało. Potem próbowałem za pomocą nowego Date, ale ustawionego tak samo jak to które wstawiałem jako klucz, wyciągnąć cośtam - a tu null :( Problem w tym, że zmienione date ma już inny hashCode i nie jest równe (w sensie metody equals) temu które było pierwotnie wstawione do mapy jako klucz.

draken pisze...

Należy jeszcze tutaj wspomnieć o precyzji danych przekazywanych w java.sql.Date (precyzja jest do dnia kalendarzowego). Jeżeli planujmemy zapisać w bazie datę z większą przecyzją (np. aż do nanosekund), używamy java.sql.Timestamp

koziołek pisze...

Pawle no nie do końca jest tak jak mówisz. java.util.Date nie jest zalecany i jak spojrzymy w API to aż mieni się ono od słowa deprecated przy nazwach metod.
Raczej przychylam się do postawy prezentowanej przez Drakena, czyli timestampów. Zresztą jeżeli przyjrzysz się jak obecnie tworzy się bazy to w przytłaczającej większości przypadków daty zapisuje się właśnie jako timestamp.

Paweł Szulc pisze...

@koziolek: moze rzeczywiscie cos w tym jest, uzywac timestampow i zapomniec o temacie...

morisil pisze...

A própowałeś:

statement.setObject(2, user.getBirthDate());

Różne to niestety działa na różnych bazach. :( Mam wrażenie że gdzieś można było modyfikować konwersję typów na poziomie JDBC.