piątek, 3 lutego 2012

Hibernate Validator (JSR 303) + mechanizm refleksji = walidacja absolutna

Przygarnięcie przez JavaEE projektu Hibernate Validator pod numerem JSR 303 było wg. mnie kolejnym dobrym krokiem w standaryzacji dobry rozwiązań z projektów opensourcowych.

Jednak po jakimś czasie używania (jakże przyjemnego) standardowych walidatorów, doszedłem do wniosku, że to za mało. Napisanie kilku własnych, które swoją drogą również tworzy się bardzo prosto, tylko na chwilę zaspokoiło moje potrzeby. Dopiero połączenie JSR 303 i mechanizmu refleksji w javie, daje maksymalne możliwości wykorzystania tego standardu.

Załóżmy, że mamy klasę która posiada dwie daty: od i do.
public class Entity {

    private Date from;
    private Date to;
}

Chcielibyśmy sprawdzić czy podane daty są po kolei, tj 'from' <= 'to'. Możemy napisać własny walidator dla klasy Entity, ale w projekcie takich klas z datami możemy mieć mnóstwo i dla każdej z nich należałoby stworzyć oddzielny walidator. Dzięki refleksją w javie możemy zrobić jeden uniwersalny, który jako parametry przyjmowałby 2 wartości, nazwa pola 'from', nazwa pola 'to'.
@Target({ TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = CheckDatesValidator.class)
public @interface CheckDates {

    String message() default "{pl.costam.CheckDates}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    String dateFrom();

    String dateTo();

}

I sam walidator:
public class CheckDatesValidator implements ConstraintValidator<CheckDates, Object> {

    private String dateFromFieldName;
    private String dateToFieldName;
    private String message;

    @Override
    public void initialize(CheckDates checkDates) {
        
        dateFromFieldName = checkDates.dateFrom();
        dateToFieldName = checkDates.dateTo();
        message = checkDates.message();
    }

    @Override
    public boolean isValid(Object object, ConstraintValidatorContext context) {

        boolean result = validateDates(object);

        if (!result) {
            
            //jeśli walidacja nie powiodła sie to tworzymy własny constraint - wskazujemy dla którego pola wystąpił błąd
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(message)
                    .addNode(dateToFieldName)
                    .addConstraintViolation();
        }

        return result;

    }

    private boolean validateDates(Object object) {

        try {
            
            Date dateFrom = field(dateFromFieldName).ofType(Date.class).in(object).get();
            Date dateTo = field(dateToFieldName).ofType(Date.class).in(object).get();

            // sprawdzenie dat
            return checkDatesInterval(dateFrom, dateTo);
        }
        catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}

Poprzez refleksję można odwołać sie do pola używając właściwie "czystej" javy, ale wygląda to średnio w kodzie i lepiej użyć jakiegoś gotowego rozwiązania. Ja osobiście polecam biliotekę: FEST

Zastowanie adnotacji jest następujące:
@CheckDates(dateFrom="from", dateTo="to")
public class Entity {

    private Date from;
    private Date to;
}

środa, 9 listopada 2011

Hibernate L1 cache a cachowanie zapytań.

Niby wszyscy wiedzą jak działa działa L1 cache w Hibernate. W ramach jednej sesji hibernata, jeśli wczytamy sobie jakiś obiekt za pomocą np. funkcji .get(), to każde kolejne wywołanie tej funkcji nie odpytuje już bezpośrednio bazy danych, tylko zwraca ten obiekt właśnie z sesji. Pytanie, czy jesteśmy świadomi wszystkich konsekwencji takiego podejścia?

Proponuje zagadkę.
W bazie jest 2 użytkowników z tego samego miasta: Andrzej i Wojtek. Metoda getAllUsersFromCity() tworzy odpowiedni obiekt Criteria i zwraca listę użytkowników z danego miasta. Przyjmijmy, że kod funkcji testCache() wykonywany jest w ramach jednej sesji hibernata:

public void testCache(){
    printUsers();
    //w tym momecie ktoś inny edytuje jednego z użytkowników,
    //w zupełnie innej sesji lub bezpośrednio na bazie,
    //zmienia nazwe z Andrzej na Bartek
    printUsers();
}

public void printUsers(){
    List<User> users = dao.getAllUsersFromCity("city");
    foreach(User user : users){
        System.out.println(user.getName());
    }
}

Co zostanie wyświetlone na ekranie?
a)AndrzejWojtekAndrzejWojtek
b)AndrzejWojtekBartekWojtek

środa, 31 sierpnia 2011

Velocity - "is not a valid reference".

Jeśli masz problem z odwołaniem się w Velocity do metody jakiegoś obiektu, np.
$someObject.getSomeField()
i dostajesz komunikat:
... is not a valid reference
To warto sprawdzić czy klasa obiektu someObject jest publiczna, w przeciwnym razie otrzymasz wspomniany wyżej komunikat.

wtorek, 28 czerwca 2011

Logika + intuicja = inteligencja ?

Rzadko mi się to zdarza, ale czasem trzeba usiąść i przewartościować to i owo w swoim życiu. Bez obaw, nie będzie to tekst o uczuciach, związkach i innych takich:) Będzie o sposobie myślenia, przyswajania wiedzy i to niekoniecznie w stricte programistycznym kontekście.

Czy zdarzyło Ci się kiedyś, że poświeciłeś 2/3 dni na jakiś problem, zadanie - bez większych rezultatów, a nagle (czasami nawet kilka dni później), nie wiadomo skąd - oświecenie - Twój mózg dostarcza rozwiązanie, praktycznie na talerzu. Ja miałem tak przy całkach, czasami potrafiłem zmarnować cały dzień na 1 całkę, żeby rano obudzić się i ją po prostu ładnie rozpisać. Jeśli chcesz wiedzieć skąd się to bierze, to koniecznie musisz przeczytać:


Tutył tcorhę nie ceikawy, ale jśeli ztsawnaaisz się delazcgo potarsfiz pzreyczatć ten tkest bez wikęzsyh porblmóew, to opdoeiwdź na m.in. to pyatine zanjzdisez w tej kąsicże.

Co do przewartościowania, to musiałem zmienić swój pogląd dotyczący wszelkich aktywności o naturze bardziej "artystycznej", które uważałem, do tej pory, za nieprzydatne i zbędne (przynajmniej dla mnie). Myślałem, że logika jest głównym silnikiem mojego mózgu. Co nie mija się mocno z prawdą, aczkolwiek każdy z nas ma drugi silnik - zupełnie nielogiczny i bardzo abstrakcyjny. Intuicja, instynkt (tak nazywany jest ten silnik) wydają się nie do końca potrzebne w przypadku np. programowania, ale tak de facto dopiero one pozwalają w pełni rozwinąć skrzydła (właściwie czymkolwiek byśmy się nie zajmowali). To właśnie silnik nr 2 pozwala obudzić się z rozwiązaną całką przed oczyma. Jego możliwości są praktycznie nieskończone jak i bardzo nieprzewidywalne. Ważne jest aby dbać i rozwijać oba silniki. Jak sugeruje autor książki, właśnie aktywności wywodzące się z szeroko rozumianej sztuki, rozwijają ten drugi silnik. Wątpię żebym nagle zaczął pisać wiersze, ale dostrzegam sens np. plastyki (i innych podobnych przedmiotów) w szkole, które (pomijając to, że były moim zdaniem po prostu źle prowadzone) mogą przydać się w ogólnym rozwoju.

Tak samo krytycznie nastawiony byłem do różnego rodzaju "niestandardowych" sposobów nauczania. Nie twierdze, że nagle wszystkie nabierają dla mnie sensu, ale cześć technik, ma swoje naukowe uzasadnienie i muszę przyznać, że chciałbym kilka z nich wypróbować. Mam nadzieję, że wystarczająco zaciekawiłem, aczkolwiek ciężko streścić książkę, która sama w sobie jest streszczeniem wielu prac i badań naukowych.

Dla tych bardziej leniwych, polecam wykład, który w dużej mierze pokrywa się z tą książką:
http://www.infoq.com/presentations/Developing-Expertise-Dave-Thomas

Wyjściowym źródłem informacji dla mnie był m.in. blog:
http://art-of-software.blogspot.com/2011/06/trawienie-confitury.html
http://art-of-software.blogspot.com/2010/01/wspinaczka-do-profesjonalizmu.html

wtorek, 21 czerwca 2011

Partial mock - stosować, czy nie?

Na samym początku chcę zaznaczyć, że prawdopodobnie ten post nie będzie udzielał jednoznacznej odpowiedzi, jego celem (przynajmniej na chwilę obecną) jest zebranie wszystkich za i przeciw stosowaniu częściowego mockowania.

Zakładam, że ewentualnemu czytelnikowi sama idea mockowania jest znana. Partial mock danego obiektu to taka hybryda prawdziwego obiektu i mocka, innymi słowy część metod danego obiektu może być zmockowana, a cześć działać normalnie. Więcej o tym:
Co to daje? Przede wszystkim ułatwia testowanie, czy raczej samo napisanie testu. Wyobraźmy sobie sytuację, w której dana klasa ma jedną publiczną metodę, która korzysta z 20 innych prywatnych, pozagnieżdżanych między sobą. Załóżmy, że te 20 metod tworzy spójną całość i wydzielanie ich do osobnych klas byłoby bezsensowne. Napisanie testu dla takiej publicznej metody jest zajęciem dość karkołomnym. Taki test, który np. sprawdzałby czy metoda 17 robi coś tam poprawnie, zawiera w sobie całą masę mocków i innych obiektów, które umożliwiają taki przepływ w kodzie klasy, aby dojść do tej metody 17 i ją przetestować. Zrozumienie takiego testu dla człowieka, który go nie pisał jest dramatem.

Czy nie lepiej napisać oddzielny test dla metody 17? A potem odpowiednio sprawdzić, czy metoda ta jest wywoływana w innej metodzie, właśnie za pomocą partial mockingu? Jak dla mnie takie podejście jest o wiele przejrzystsze.

Konserwatywni w dziedzinie testowania, na pewno już się gotują, że piszę o testowaniu metod prywatnych, ale powiedzmy, że zmieniam im dostęp na pakietowy:). Tak, czy inaczej na pewno nie pochwalą partial mocków, bo to podejście śmierdzi, jest "niekoszerne".

Ogólnie zgadzam się z nimi w jednej kwestii, na pewno konieczność zastosowanie partial mockingu do testów, jest bardzo wyraźnym sygnałem, że z naszym kodem jest coś nie tak i prawdopodobnie lepszym wyjściem będzie jego refaktoring.

Jednakże istnieją sytuacje (jak chociażby legacy code), gdzie partial mock jest nie dość, że wygodnym, to czytelnym rozwiązaniem.

Oczywiście nie jestem w dziedzinie testów aż takim ekspertem jakbym chciał, dlatego jeśli istnieją inne przesłanki za lub przeciw partial mockom, to proszę o komentarze.

Artykuły o partial mocking:
http://nathan.crause.name/entries/programming/partial-mocking-stubbing-and-why-it-s-not-evil
http://monkeyisland.pl/2009/01/13/subclass-and-override-vs-partial-mocking-vs-refactoring/
http://stackoverflow.com/questions/3806977/partial-mocking-as-code-smell

poniedziałek, 18 kwietnia 2011

Zagadka java Date()

Jaki będzie wynik poniższego kodu:

System.out.println(new Date().getTime() < new Date().getTime());

piątek, 8 kwietnia 2011

Equals, hashCode i Hibernate

Ok, to był dość długi wstęp (1, 2, 3), może czasami jak dla dzieci, ale przynajmniej wszystko jest w 1 miejscu, a składa się na meritum problemu, czyli jak powinno się implementować metody equals() i hashCode() w połączeniu z Hibernatem. Mówiąc jeszcze bardziej precyzyjnie, czy możną używać pola reprezentującego klucz główny tabeli w tych metodach.


Najpierw trochę literatury:
http://community.jboss.org/wiki/EqualsandHashCode

https://forum.hibernate.org/viewtopic.php?t=928172

http://onjava.com/pub/a/onjava/2006/09/13/dont-let-hibernate-steal-your-identity.html?page=2

Wnioski z tego, z czym się do tej pory zapoznałem, są takie, że Hibernate mówi: nie używajcie ID, ale jak już musicie, to przynajmniej upewnijcie się, że obiekty (które będą porównywane) są w tej samej sesji! Dlatego proponuję otwartą dyskusję na ten temat. Obecnie przyjąłem sobie następujący schemat pisania equals() i hashCode(). W equals() biorę pod uwagę tylko id jeśli oba obiekty mają ustawione id (w moim przypadku różne od 0). W przeciwnym wypadku porównuję odpowiednie pola. W hashCode() nie wykorzystuje id, ponieważ może dojść do sytuacji, w której 2 obiekty takie same, będą miały różne hashe.
    @Override
    public boolean equals(Object object) {
        
        if (this == object) return true;
        
        if ( !(object instanceof Entity) ) return false;
        
        Entity entity = (Entity) object;
        
        if (id != 0 && entity.getId()!=0){            
            return id == entity.getId();
        }
        return new EqualsBuilder().append(field, entity.getField())
            ...//i inne pola
            .isEquals();
   }