Użycie SpEL w repozytoriach springa

0

Mam klase:

public class MyFilter {
    public String titleFilter() {
        return "%title%";
    }

    public Long[] idFilter() {
        return new Long[] {
                1L, 2L
        };
    }
}

Oraz encję:

@Entity
public class MyEntity implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotNull
    @NotBlank
    @Size(min = 1, max = 96)
    @Column(name = "title")
    private String title;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}

A także repozytorium:

@Repository
public interface MyRepository extends JpaRepository<MyEntity, Long>, JpaSpecificationExecutor<MyEntity> {
    @Query("SELECT e FROM mypackage.data.entity.MyEntity e " +
            "WHERE e.title LIKE :#{#filter.titleFilter()}"
    )
    Page<MyEntity> list1(@Param("filter") MyFilter filter, Pageable pageable);


    @Query("SELECT e FROM mypackage.data.entity.MyEntity e " +
            "WHERE e.title LIKE :#{#filter.titleFilter()} AND e.title LIKE :#{#filter.titleFilter()}"
    )
    Page<MyEntity> list2(@Param("filter") MyFilter filter, Pageable pageable);


    @Query("SELECT e FROM mypackage.data.entity.MyEntity e " +
            "WHERE e.id IN :#{#filter.idFilter()}"
    )
    Page<MyEntity> list3(@Param("filter") MyFilter filter, Pageable pageable);


    @Query("SELECT e FROM mypackage.data.entity.MyEntity e " +
            "WHERE e.id IN :#{#filter.idFilter()} AND e.id IN :#{#filter.idFilter()}"
    )
    Page<MyEntity> list4(@Param("filter") MyFilter filter, Pageable pageable);


    @Query("SELECT e FROM mypackage.data.entity.MyEntity e " +
            "WHERE e.title LIKE :#{#filter.titleFilter()} AND e.id IN :#{#filter.idFilter()}"
    )
    Page<MyEntity> list5(@Param("filter") MyFilter filter, Pageable pageable);
}

Dlaczego metoda list5 generuje poniższy wyjątek?

org.hibernate.QueryException: unexpected char: '#' [SELECT e FROM mypackage.data.entity.MyEntity e WHERE e.title LIKE :#{#filter.titleFilter()} AND e.id IN :__$synthetic$__2]	
0

Ten drugi # chyba jest niepotrzebny:

@Query("SELECT e FROM mypackage.data.entity.MyEntity e " +
            "WHERE e.title LIKE :#{filter.titleFilter()} AND e.id IN :#{filter.idFilter()}"
0

Niestety, raczej jest potrzebny. Po zastąpieniu wszystkich "#filter" na "filter" nie działa żadna z powyższych metod (przed zmianami nie działała tylko ostatnia). Leci wyjątek:

Caused by: java.lang.IllegalStateException: Using named parameters for method public abstract org.springframework.data.domain.Page mypackage.data.repository.MyRepository.list3(mypackage.data.filter.MyFilter,org.springframework.data.domain.Pageable) but parameter 'filter' not found in annotated query 'SELECT e FROM mypackage.data.entity.MyEntity e WHERE e.id IN :#{filter.idFilter()}'!
0

@karolinaa: metody oparte o nazewnictwo typu findByTitleLike sa fajne w prostych sytuacjach typu "chce listę użytkowników, których nazwa pasuję do określonego wzorca". Problem zaczyna sie wtedy, kiedy na tej liscie chcesz zawrzec zarowno nazwy użytkowników jak i sumę pieniędzy, które wpłacili płacąc za jakąś usługe i dodatkowo wyświetlić tylko tych, którzy zapłacili wiecej niż X PLN. Chodzi więc o efekt, który można uzyskać zapytaniem podobnym do tego:

SELECT u.name, sum(p.amount) FROM users u
LEFT JOIN payments p ON p.user_id = u.id
WHERE sum(p.amount) > 100
GROUP BY u.name

Dodam, że moje zapytania mogą być bardziej skomplikowane i mogą sumować (i nie tylko sumować) wartości w ramach wielu powiązanych ze sobą tabel. Jeżeli masz koncepcje jak rozwiązać to lepiej to chętnie poczytam.

Chciałbym jednak zaznaczyć, że w moim przypadku dane mogą być filtrowane po wielu cechach (powyższy przykład zawiera filtrowanie po jednej cesze, czyli po sumie wpłat). Tych cech będzie od kilku do prawdopodobnie kilkunastu. Użytkownik też nie musi filtrować danych po wszystkich cechach - może użyć powiedzmy 2 z 10, ale może także użyć 10 z 10.

0

@tk czemu a czemu nie chcesz kryterionow czy jak????? o SPeL nie chce mi się czytać.... ;p

1

Czy przypadkiem problem, o ktorym piszesz, to nie jest potrzeba stworzenia dynamicznego zapytania (skladajacego sie z roznych fragmentow SQL), w zaleznosci od parametrow wejsciowych?

W przypadku bardzo skomplikowanego dynamicznego SQL korzystalem kiedys z takiego pakietu http://www.jooq.org/. Na koncu zwracalem dynamicznego SQL-a, ktory byl ladowany do EntityManagera. Nie wszystko dawalo sie zrobic JPA. Z jOOQ skomplikowane query z bardzo duza iloscia dynamiki (czasem dynamiczne JOINy, czasem dynamiczne WHERE to bulka z maslem). Inna zaleta jest wyplucie prostego jak ruski czlog SQLa, ktory latwo wytestowac w konsolce SQL (na pewno latwiej niz JPQL).

Do prostszych, dynamicznych zapytan korzystalem z JPA Criteria API (generowalo rozne query, w zaleznosci parametrow wejsciowych).
https://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/Criteria
Poczatkowo mniej czytelne jak JPQL, ale idzie ogarnac. Na pewno jest to proszte niz konkatencja stringow JPQL/SQL.

Czysty Hiberanate ma wlasne Criteria, ktore sa ciut czytelniejsze (ale nie korzystalem).

0

bierz hinernate jako dostawce JPA i tutaj masz też przykładzik ;] ------> http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#specifications

0

Poza tym nie znam SpEL, ale z jednego powodu rozwiazanie to wydaje mi sie beznadziejne: strasznie trudno napisac do niego test (jednostkowy, integracyjny powien byc prostszy, jesli zaladuje sie kontekst Springa, ale trzeba miec dane, aby taki test mial sens). Wolalbym juz z dwojego zlego robic konkatenacje (poniewaz latwiej ja wytestowac jednostkowo). Co nie zmienia faktu, ze konkatencja ssie, bo mamy Criteria. Ale kiedys nie bylo, a czasem sie pracuje ze starymi sytemami. :P

0

Mialem na mysli oczywiscie test sprawdzajcy czy dynamiczne zbudowane query zwrocilo dobry String (czasem to sie przydawalo przy bardzo zlozonych zapytaniach, dla oceny kompatybilnosci wstecznej). Nie mam na mysli testowania DAO.

0

może to zależy od systemu, ale jak dla mnie testowanie czegoś takiego to.... sory

0

Konkatencja to cos co sie moze brzydko mowiac latwo spier**. Dlatego kiedys pisalem testy do dynamicznych zapytan w czystym SQLu. :P Nie testowalem dzialania zapytania, a to czy String sie dobrze zlozyl. W adnotacjach jest jeszcze gorzej, bo sa cholernie trudne w ocenie czy nie bylo regresji.

0

W przypadku JPQL nie trzeba, bo @NamedQuery jest dynamicznie kompilowane na etapie deployu: można powiedzieć, że maszyna to testuje. Aplikacja się nie włączy. W przypadku @NamedNativeQuery / natywny SQL nie ma już tak lekko i takie błędy wychodzą dopiero runtime.

0
karolinaa napisał(a):

@tk czemu a czemu nie chcesz kryterionow czy jak????? o SPeL nie chce mi się czytać.... ;p

bierz hinernate jako dostawce JPA i tutaj masz też przykładzik ;] ------> http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#specifications

Nie wiem jakie jest Twoje zdanie o Criteria API, ale moim zdaniem to rozwiazanie sprawdza sie troszeczke lepiej w sytuacji kiedy trzeba stworzyc pewien uniwersalny mechanizm - np. gdybysmy chcieli stworzyc mechanizm repozytorium, ktory akurat zrobili juz tworcy Springa. Gorzej sie sprawdza w takich przypadkach jak moj, typu "zlacz kilka konkretnych tabel, policz sume jakis wartosci itd."

Powyzszy opinia dotyczy zapytan tworzonych od podstaw. Innymi slowy: jezeli mam tworzyc zapytanie poczawszy od selecta porzez joiny i where, na group by konczac to wole to robic w JPQL / SQL niz w Criteria API, ktore jest troche bardziej toporne (opinia subiektywna).

Duzo lepiej sprawuja sie natomiast specyfikacje, o ktorych wspomnialas ale jak mam za ich pomoca zwrocic tę sumę SUM(p.amount), ktorą podałem w poprzednim przykładzie?

NiebieskiFlaming napisał(a):

Czy przypadkiem problem, o ktorym piszesz, to nie jest potrzeba stworzenia dynamicznego zapytania (skladajacego sie z roznych fragmentow SQL), w zaleznosci od parametrow wejsciowych?

Mozna powiedziec, ze jestem zainteresowany stworzeniem dynamicznego zapytania, niekoniecznie opartego o SQL (moze byc JPQL), jednak jego dynamika jest raczej niewielka. Na tyle mala, ze SpEL w moim przypadku by sie spokojnie mogl sprawdzic. Chodzi o cos w stylu:

select [lista kolumn, sum itd] from [jakas tabela]
[jakies zlaczenia]
where
(kolumna1 LIKE [wyrazenie SpEL]) AND 
(kolumna2 LIKE [wyrazenie SpEL]) AND 
(kolumna3 LIKE [wyrazenie SpEL])
group by [lista kolumn]

Cala dynamika polegac bedzie na tym, ze jezeli uzytkownik bedzie chcial filtrowac dane po kolumna1 to SPEL w tym miejscu zwroci cos w stylu %jakis wzorzec%, natomiast jezeli uzytkownik nie bedzie chcial filtrowac po kolumna1 to zwroci %% (dowolny ciag znakow). Taki mechanizm mi wystarczy.

Dodam, ze wyrazenie SpEL nie powoduje nic innego jak wywloanie metody, ktora wygeneruje %jakis wzorzec%, lub %% w zaleznosci od sytuacji. Te metode mozna jak najbardziej przetestowac jednostkowo.

Oczywiscie zamiast LIKE czasem mozne pojawic sie operator rownosci czy inne operatory, ale mimo wszystko idea jest taka jak wyzej i to co wyzej mi wystarczy.

Generalnie wszystko byloby ok, gdyby nie to ze leci wyjatek - nie mialem problemu w sytuacji kiedy nie uzywalem klauzuli IN.

Mimo wszystko dzieki za informacje o jOOQ - wydaje mi sie, ze jest to technologia bardziej zaawansowana niz potrzebuje, ale zapewne warto o tym poczytac wiec dzieki.

0

Nie wiem jakie jest Twoje zdanie o Criteria API, ale moim zdaniem to rozwiazanie sprawdza sie troszeczke lepiej w sytuacji kiedy trzeba stworzyc pewien uniwersalny mechanizm - np. gdybysmy chcieli stworzyc mechanizm repozytorium, ktory akurat zrobili juz tworcy Springa. Gorzej sie sprawdza w takich przypadkach jak moj, typu "zlacz kilka konkretnych tabel, policz sume jakis wartosci itd."

Ja mam produkcyjne doswiadczenie wlasnie jesli chodzi o budowanie dynamicznych zapytan (do REST oraz filtrow formularzy) z uzyciem JPA Criteria API. Z Criteria szlo to dosc sprawnie. Jednak krzywa uczenia sie dosc dluga w stosunku do JPQL, trzeba szukac rozwiazan na stackoverlow.com. Nie jest to najbardziej czytelne API swiata. Ale jak raz sie nauczylem to dziala wszedzie. Zgoda, ze z jego uzyciem zbudowalem analogiczna klasa do CrudRepository (generyczny CRUD dla JEE, ktorego nie ma standardzie).

Wada jOOQ jest tez koniecznosc aktaulizacji wygenerownych klas, ktore sluza do budowania dynamicznych zapytan. Ale mozna sobie napisac skrypt i nie ma problemu. Generalnie patrzac jakie problemy rozwiazuje ta technologia jest tego warta (przynajmniej byla w moim przypadku).

Pozdawiam,

1 użytkowników online, w tym zalogowanych: 0, gości: 1