Spring Data - Query by Example

0

Mam serwis pisany w Kotlinie który pobiera dane z Mongo poprzez Spring Data. Dane dotyczą różnego rodzaju ofert zakupowych i oferty te mają wiele właściwości - około 30.
Wszystko ładnie śmiga ale chcę zrobić wyszukiwarkę do szukania ofert na podstawie różnych kluczy jednocześnie, np. pobieranie ofert po typie oferty, cenie, lokalizacji itd itd.
Pytanie jak to sensownie zrobić tak aby baza zwróciła listę ofert które spełniają dane kryteria?
Najsensowniejszy pomysł jaki znalazłem to Query by Example, tutaj przykład:
https://github.com/spring-projects/spring-data-examples/tree/master/mongodb/query-by-example
I to wygląda sensownie natomiast u mnie to się nie sprawdza bo aby tak filtrować to muszę przygotować obiekt oferty a w moim przypadku jest on niemutowalny i wymaga wypełnienia wszystkich pól przez konstruktor (oczywiście not null).
Ma ktoś inny pomysł jak można to sensownie zrobić?

0

I to wygląda sensownie natomiast u mnie to się nie sprawdza bo aby tak filtrować to muszę przygotować obiekt oferty a w moim przypadku jest on niemutowalny i wymaga wypełnienia wszystkich pól przez konstruktor (oczywiście not null).

A gdybyś utworzył osobny obiekt, mutowalny któy by służył tylko temu żeby być klasą pod te filtry? Nie próbowałem, ale znając możliwości Springa to nawet osobne repo pod to możesz mieć tylko na tej klasie adnotacja @Document z odpowiednim collectionName

0
hcubyc napisał(a):

A gdybyś utworzył osobny obiekt, mutowalny któy by służył tylko temu żeby być klasą pod te filtry?
Nie próbowałem, ale znając możliwości Springa to nawet osobne repo pod to możesz mieć tylko na tej klasie adnotacja @Document z odpowiednim collectionName

Próbowałem i niestety coś nie idzie.
Próbowałem na 2 sposoby.

  1. Utworzyłem nową klasę mutowalną z listą parametrów ktróre mają identyczne nazwy jak stałe w ofercie i dodałem adnotację @Document z taką samą nazwą kolekcji. W MongoRepository istnieje już metoda:
	<S extends T> List<S> findAll(Example<S> example);

ale ze względu na to że generyk Example musi być typu Offer to nie mogę jej nadpisać tylko mogę dodać nową.
Próbowałem więc tworzyć nowe typu

  fun findBy(example: Example<OfferParams>) : Offer

i to się kompiluje ale niestety jak widzę po logach to idzie zapytanie do bazy o pobranie wszystkiego bez żadnych kryteriów.

  1. Spróbowałem wiec z drugim repo ale to też jest lipa bo jak dodam takie repo:
interface AccountRepository : MongoRepository<OfferParams, String>

to moja metoda findAll do której faktycznie mogę teraz przekazać OfferParams zwróci listę obiektów ale nie Offer a OfferParams. Jest to oczywiscie bezsensu bo tam nie mam wszystkich pól.
Mogę uzupełnić brakujące pola ale też wydaje mi się to słabe bo będę miał 2 takie same klasy z tym że jedna będzie mutowalna a druga nie. Po zmianie oferty zawsze trzeba będzie też modyfikować OfferParams co jest moim zdaniem słabe.

1

kolejny hack - do tego drugiego podejścia dołóż projekcje?
https://stackoverflow.com/questions/25393096/how-to-use-projecting-types-in-spring-data-mongodb-repositorys-query-methods

no i podejście bez hacków - tworzysz sobie taką mutowalną klase, tworzysz sobie interfejs repo - osobny badż zamiast twojego repo do Offer i ręcznie implementujesz to za pomocą mongoTemplate/mognoReactiveTemplate czy jak tam sie ta nowa nazywa. I wtedy implementując QBE po prostu dodajesz kolejne pola do Query jeżeli nie są nullem. Jako klase zwrotną podasz Offer i wszystko będzie banglać

osobiście bym polecil rozwiązanie bez hacków, bo każda nowa osoba niezaleznie czy zna springa czy nie szybko się połapie o co chodzi

0
hcubyc napisał(a):

osobiście bym polecil rozwiązanie bez hacków, bo każda nowa osoba niezaleznie czy zna springa czy nie szybko się połapie o co chodzi

Też o tym myślałem ale wolałem zostawić to na koniec. Myślałem że skoro spring i tak już tyle magii robi to może ten problem przy okazji także rozwiązuje. Póki co jednak nie widzę innego rozwiązania tak więc pozostanie mi faktycznie implementowanie tego samemu. Dzięki.

0

A może użyć ElasticSearcha? Używałem w jednym projekcie, działał naprawdę szybko i myślę, że spokojnie poradzi sobie z wyszukiwaniem po wielu atrybutach tak jak u Ciebie. Przyda się tez jak dojdzie Ci później wszyszukiwanie pełnotekstowe.

0

Miałem podobny problem nie wiem na ile spodoba Ci się te rozwiązanie ale wymyśliłem coś takiego:

  1. Zrobisz sobie klasę do filtrowania z polami które sie nazywają tak samo jak w dokumencie którego szukasz
public interface CustomQueryRepository<T> {

    default Query createQuery(T filters, MongoTemplate mongoTemplate) {
        BasicDBObject dbDoc = new BasicDBObject();
        mongoTemplate.getConverter().write(filters, dbDoc);
        dbDoc.remove("_class");
        Criteria[] criteria = dbDoc.keySet()
                .stream()
                .map(key -> Optional.ofNullable(dbDoc.get(key)).map(value -> createCondition(key, value)))
                .filter(Optional::isPresent)
                .map(Optional::get)
                .flatMap(Collection::stream)
                .toArray(Criteria[]::new);
        Query query = new Query();
        query.addCriteria(new Criteria().andOperator(criteria));

        return query;
    }

    default List<Criteria> createCondition(String key, Object value) {
        if (value instanceof String) {
            return Arrays.asList(where(key).is(value));
        }
        else if(value instanceof Document) {
            return ((Document) value).entrySet()
                    .stream()
                    .flatMap(entry -> createCondition(String.format("%s.%s", key, entry.getKey()), entry.getValue()).stream())
                    .collect(Collectors.toList());
        }
        return (List<Criteria>) ((BasicDBList) value).toMap()
                .values()
                .stream()
                .flatMap(doc -> createCondition(key, doc).stream())
                .collect(Collectors.toList());
    }
}
  1. Teraz w serwisie (czy gdzie tam chcesz) używasz mongoTemplate.findAll(createQuery(TwojaKlasaDoFiltrow, mongoTemplate), TwojDocument.class)

W tej metodzie createCondition obsługuję tylko Stringi, Listy, i zagnieżdzone obiekty. Jak masz jakieś inty longi to musiałbyś sobie pewnie dopisać nowego ifa.

Edit:
Jeszcze teraz widzę jest opcja na coś takiego:
Query query = new Query(new Criteria().alike( Example.of(filters)));
i wtedy
mongoTemplate.findAll(query, TwojDocument.class)
tylko musiałbyś zobaczyć jak wygląda te query czy dodaje tam szukanie po _class

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