Najpierw lektura specyfikacji JPA - rozdział 9.1.21 Enumerated Annotation (strona 181):
Domyślnie pole typu wyliczeniowego utrwalane (zapisywane) jest w bazie danych po jego liczbie porządkowej (wywołanie metody java.lang.Enum.ordinal()). Za pomocą adnotacji @Enumerated określany jest sposób mapowania pola typu wyliczeniowego, jako uzupełnienie dane z adnotacji @Basic (oczywiście adnotacja nie jest konieczna w wielu sytuacjach, więc najczęściej bazuje się na wartościach domyślnych adnotacji @Basic). Klasa java.lang.Enum, która reprezentuje wszystkie typy wyliczeniowe w Javie dostarcza dwie metody - ordinal() oraz name(), które uczestniczą w mapowaniu pola wyliczeniowego na struktury bazodanowe. Pierwsza z nich ordinal() wywoływana jest przy konfiguracji ORDINAL dla adnotacji @Enumerated, a druga przy wartości STRING. Domyślnie zakłada się ORDINAL, więc wszystkie typy wyliczeniowe będą zapisywane w bazie jako liczby porządkowe. Za pomocą STRING do bazy trafiają nazwy wartości typu wyliczeniowego.
Adnotacje posiadają swoje odpowiedniki w postaci elementów w pliku konfiguracji mapowania JPA, którym domyślnie jest META-INF/orm.xml (inne można wskazać za pomocą elementu mapping-file w META-INF/persistence.xml - "sercu" konfiguracji JPA lub nie wprost poprzez element jar-file). Odpowiednikiem xmlowym (zawartym w pliku orm.xml) adnotacji @Enumerated jest element enumerated (podelement basic), który akceptuje dwie wartości ORDINAL (domyślna) i STRING.
W mojej aplikacji Przychodnia zadeklarowałem typ wyliczeniowy pl.jaceklaskowski.przychodnia.model.Plec (początkowo był on w ramach klasy Pacjent, ale okazało się, że JDA narzekał na zagnieżdzenie klas wykorzystywanych do mapowania JPA).
package pl.jaceklaskowski.przychodnia.model;W pliku mapowania JPA - META-INF/orm.xml - wystarczyło dodać
public enum Plec {
KOBIETA('K', "Kobieta"), MEZCZYZNA('M', "Mężczyzna");
private final char kod;
private final String nazwa;
Plec(char kod, String nazwa) {
this.kod = kod;
this.nazwa = nazwa;
}
public char kod() {
return kod;
}
@Override
public String toString() {
return this.nazwa;
}
public static Plec valueOf(int cyfra) {
return cyfra % 2 == 0 ? KOBIETA : MEZCZYZNA;
}
}
<basic name="plec" optional="false">aby do bazy trafiały napisy w postaci MEZCZYZNA lub KOBIETA. Nie rozwiązałem jeszcze kwestii zapisywania samych kodów typów, np. M dla mężczyzny, a K dla kobiety (ma ktoś pomysł jak to zrealizować, aby kod typu był wartością w bazie danych?).
<enumerated>STRING</enumerated>
</basic>
Przy okazji zastosowania typu wyliczeniowego skorzystałem ze statycznego importu (import static) i kod stał się bardziej przejrzysty i czytelny. Wychodzę z założenia, że najpierw działający kod w wersji 1.0, a później jego uaktrakcyjnianie w kolejnych wersjach. W ten sposób podszedłem właśnie do zadania aplikacji Przychodnia.
Dodatkowo pola plec oraz wiek są ustawiane na podstawie obowiązkowego peselu, co sprowadziło się do następującej konstrukcji:
protected void ustawWiekPlecNaPodstawiePeselu() {Coś mi mówi, że można byłoby to zrealizować prościej, ale nic na szybko nie przyszło mi do głowy, poza owym if-(else if)*-else. Sugestie mile widziane.
// za Wikipedią o wieku w PESELu:
// http://pl.wikipedia.org/wiki/PESEL#P.C5.82e.C4.87
// Numeryczny zapis daty urodzenia przedstawiony jest w następującym porządku:
// dwie ostatnie cyfry roku, miesiąc i dzień.
// Dla odróżnienia poszczególnych stuleci przyjęto następującą metodę kodowania:
// * dla osób urodzonych w latach 1900 do 1999 - miesiąc zapisywany jest w sposób naturalny
// * dla osób urodzonych w innych latach niż 1900 - 1999 dodawane są do numeru miesiąca następujące wielkości:
// o dla lat 1800-1899 - 80
// o dla lat 2000-2099 - 20
// o dla lat 2100-2199 - 40
// o dla lat 2200-2299 - 60
int rokDzisiaj = Calendar.getInstance().get(Calendar.YEAR);
int rok = Integer.valueOf(this.pesel.substring(0, 2));
int miesiac = Integer.valueOf(this.pesel.substring(2, 4));
if (miesiac > 80) {
miesiac -= 80;
rok += 1800;
} else if (miesiac > 60) {
miesiac -= 60;
rok += 2200;
} else if (miesiac > 40) {
miesiac -= 40;
rok += 2100;
} else if (miesiac > 20) {
miesiac -= 20;
rok += 2000;
} else {
rok += 1900;
}
this.wiek = rokDzisiaj - rok;
if (this.wiek <= 0) {
throw new IllegalArgumentException("Niedozwolony wiek " + this.wiek + " [PESEL:" + this.pesel + "]");
}
// za Wikipedią o płci w PESELu:
// http://pl.wikipedia.org/wiki/PESEL#P.C5.82e.C4.87
// Informacja o płci osoby, której zestaw informacji jest identyfikowany,
// zawarta jest na 10 (przedostatniej) pozycji numeru PESEL.
// * cyfry parzyste (0, 2, 4, 6, 8) – oznaczają płeć żeńską
// * cyfry nieparzyste (1, 3, 5, 7, 9) – oznaczają płeć męską
this.plec = Plec.valueOf(Integer.valueOf(this.pesel.substring(9, 10)) % 2);
}
Niestety, sądziłem, że ta zmiana pociągnie za sobą zmiany w tworzonej dynamicznie tabeli przez JDA, tak że użytkownik będzie mógł wybrać płeć pacjenta z listy tworzonej na podstawie możliwych wartości typu wyliczeniowego, ale nic takiego się nie wydarzyło. W zamian pole stało się niemodyfikowalne, co w zasadzie jest równie dobre, gdyż jego wartość i tak wyliczana jest na podstawie obowiązkowego peselu.
Jutro zaplanowałem zabawę z formatowaniem danych w JTable podczas wyświetlania (renderery komórek) i edycji (edytory komórek).
Jacku z moich doświadczeń z PESELem wynika, że popełniłeś "standardowy błąd numer 1", czyli zapisałeś numer pesel jako Integer. Znacznie łatwiej zapisać go jako String i wtedy analizować. Tak naprawdę w całym procesie nie trzeba ani razu wykonywać zamiany Integer-String, a jedynie wybierać poszczególne znaki z numeru pesel.
OdpowiedzUsuńPesel jest stringiem. Nawet korzystam z metody substring(). Skąd przypuszczenie, że mam go jako int?
OdpowiedzUsuńA wyciąganie danych z pesela poprawne? Coś wydaje mi się, że możnaby prościej.
Jacek
if'y można "stablicować":
OdpowiedzUsuńint[] mieK = new int[]{0,20,40,60,80};
int[] rokK = new int[]{1900,2000,2100,2200,1800};
rok += rokK[miesiac/20];
miesiac -= mieK[miesiac/20];
miesiac może być max. 99 więc powinno działać ok. Oczywiście można dyskutować nad jakością tego pomysłu.