Jaka jest różnica między MongoTemplate Spring Data a MongoRepository?

102

Muszę napisać aplikację, w której będę mógł wykonywać złożone zapytania przy użyciu spring-data i mongodb. Zaczynałem od korzystania z MongoRepository, ale zmagałem się ze złożonymi zapytaniami, aby znaleźć przykłady lub faktycznie zrozumieć składnię.

Mówię o zapytaniach takich jak to:

@Repository
public interface UserRepositoryInterface extends MongoRepository<User, String> {
    List<User> findByEmailOrLastName(String email, String lastName);
}

lub użycie zapytań opartych na JSON, które próbowałem metodą prób i błędów, ponieważ nie rozumiem właściwej składni. Nawet po przeczytaniu dokumentacji mongodb (niedziałający przykład z powodu złej składni).

@Repository
public interface UserRepositoryInterface extends MongoRepository<User, String> {
    @Query("'$or':[{'firstName':{'$regex':?0,'$options':'i'}},{'lastName':{'$regex':?0,'$options':'i'}}]")
    List<User> findByEmailOrFirstnameOrLastnameLike(String searchText);
} 

Po przeczytaniu całej dokumentacji wydaje się, że mongoTemplatejest ona wtedy znacznie lepiej udokumentowana MongoRepository. Mam na myśli następującą dokumentację:

http://static.springsource.org/spring-data/data-mongodb/docs/current/reference/html/

Czy możesz mi powiedzieć, co jest wygodniejsze i wydajniejsze w użyciu? mongoTemplateczy MongoRepository? Czy oboje są tak samo dojrzali, czy jednemu z nich brakuje więcej funkcji niż drugiemu?

Christophera Armstronga
źródło

Odpowiedzi:

136

„Wygodne” i „wydajne w użyciu” są w pewnym stopniu sprzeczne z celami. Repozytoria są zdecydowanie wygodniejsze niż szablony, ale te ostatnie dają oczywiście bardziej szczegółową kontrolę nad tym, co należy wykonać.

Ponieważ model programowania repozytorium jest dostępny dla wielu modułów Spring Data, bardziej szczegółową dokumentację można znaleźć w sekcji ogólnej dokumentacji źródłowej Spring Data MongoDB .

TL; DR

Ogólnie zalecamy następujące podejście:

  1. Zacznij od streszczenia repozytorium i po prostu zadeklaruj proste zapytania przy użyciu mechanizmu wyprowadzania zapytań lub zapytań zdefiniowanych ręcznie.
  2. W przypadku bardziej złożonych zapytań dodaj ręcznie implementowane metody do repozytorium (zgodnie z dokumentacją tutaj). Do realizacji użyj MongoTemplate.

Detale

Na przykład wyglądałoby to mniej więcej tak:

  1. Zdefiniuj interfejs dla swojego kodu niestandardowego:

    interface CustomUserRepository {
    
      List<User> yourCustomMethod();
    }
    
  2. Dodaj implementację dla tej klasy i postępuj zgodnie z konwencją nazewnictwa, aby upewnić się, że możemy znaleźć klasę.

    class UserRepositoryImpl implements CustomUserRepository {
    
      private final MongoOperations operations;
    
      @Autowired
      public UserRepositoryImpl(MongoOperations operations) {
    
        Assert.notNull(operations, "MongoOperations must not be null!");
        this.operations = operations;
      }
    
      public List<User> yourCustomMethod() {
        // custom implementation here
      }
    }
    
  3. Teraz pozwól, aby Twój podstawowy interfejs repozytorium rozszerzył niestandardowy, a infrastruktura automatycznie użyje Twojej niestandardowej implementacji:

    interface UserRepository extends CrudRepository<User, Long>, CustomUserRepository {
    
    }
    

W ten sposób zasadniczo masz wybór: wszystko, co jest łatwe do zadeklarowania, wchodzi w skład UserRepository, wszystko, co lepiej zaimplementowano ręcznie, wchodzi do CustomUserRepository. Opcje dostosowywania są udokumentowane tutaj .

Oliver Drotbohm
źródło
1
Cześć Oliver, to właściwie nie działa. spring-data próbuje automatycznie wygenerować zapytanie z niestandardowej nazwy. yourCustomMethod (). Powie "twoje" nie jest prawidłowym polem w klasie domeny. Postępowałem zgodnie z instrukcją, a także dwukrotnie sprawdziłem, jak to robisz, na przykładach spring-data-jpa. Brak szczęścia. spring-data zawsze próbuje wygenerować automatycznie, gdy tylko rozszerzam niestandardowy interfejs na klasę repozytorium. Jedyna różnica polega na tym, że używam MongoRepository, a nie CrudRepository, ponieważ na razie nie chcę pracować z iteratorami. Jeśli masz wskazówkę, będzie to mile widziane.
Christopher Armstrong,
11
Najczęstszym błędem jest niewłaściwa nazwa klasy implementacji: jeśli wywoływany jest interfejs bazowego repozytorium, należy nazwać YourRepositoryklasę implementacji YourRepositoryImpl. Czy tak jest? Jeśli tak, z przyjemnością
obejrzę
5
Cześć Oliver, klasa Impl została źle nazwana, jak przypuszczałeś. Poprawiłem nazwę i wygląda na to, że teraz działa. Bardzo dziękuję za Twoją opinię. Naprawdę fajnie jest móc w ten sposób używać różnych opcji zapytań. Przemyślane!
Christopher Armstrong
Ta odpowiedź nie jest taka jasna. Po zrobieniu wszystkiego w tym przykładzie przechodzę do tego problemu: stackoverflow.com/a/13947263/449553 . Konwencja nazewnictwa jest więc bardziej rygorystyczna, niż wygląda to na tym przykładzie.
msangel
1
Klasa implementacji w # 2 ma niewłaściwą nazwę: powinna być, CustomUserRepositorya nie CustomerUserRepository.
Cotta
27

Ta odpowiedź może być nieco opóźniona, ale radziłbym omijać całą trasę repozytorium. Otrzymujesz bardzo mało wdrożonych metod o dużej wartości praktycznej. Aby to zadziałało, napotykasz bzdury dotyczące konfiguracji Javy, nad którymi możesz spędzać dni i tygodnie bez większej pomocy w dokumentacji.

Zamiast tego idź z MongoTemplatetrasą i utwórz własną warstwę dostępu do danych, która uwolni Cię od koszmarów konfiguracyjnych, z którymi borykają się programiści Spring. MongoTemplatejest naprawdę zbawieniem dla inżynierów, którzy czują się komfortowo przy tworzeniu własnych klas i interakcji, ponieważ zapewnia dużą elastyczność. Struktura może wyglądać mniej więcej tak:

  1. Utwórz MongoClientFactoryklasę, która będzie działać na poziomie aplikacji i da ci MongoClientobiekt. Możesz zaimplementować to jako Singleton lub używając Enum Singleton (jest to bezpieczne wątkowo)
  2. Utwórz klasę bazową dostępu do danych, z której możesz dziedziczyć obiekt dostępu do danych dla każdego obiektu domeny). Klasa bazowa może implementować metodę tworzenia obiektu MongoTemplate, którego metody klasy mogą używać dla wszystkich dostępów do bazy danych
  3. Każda klasa dostępu do danych dla każdego obiektu domeny może implementować podstawowe metody lub można je zaimplementować w klasie bazowej
  4. Metody kontrolera mogą następnie w razie potrzeby wywoływać metody w klasach dostępu do danych.
rameshpa
źródło
Cześć @rameshpa. Czy mogę używać zarówno MongoTemplate, jak i repozytorium w tym samym projekcie? .. Czy można używać
Gauranga
1
Możesz, ale implementowany MongoTemplate będzie miał inne połączenie z bazą danych niż połączenie używane przez Repozytorium. Atomowość może być problemem. Również nie polecałbym używania dwóch różnych połączeń na jednym wątku, jeśli masz potrzeby sekwencjonowania
rameshpa
26

FWIW, dotyczące aktualizacji w środowisku wielowątkowym:

  • MongoTemplatezapewnia „atomowy” out-of-the-box operacje updateFirst , updateMulti, findAndModify, upsert..., które pozwalają modyfikować dokument w jednej operacji. UpdateObiekt używany przez tych metod również pozwala kierować tylko odpowiednie pola .
  • MongoRepositorytylko daje podstawowe operacje CRUD find , insert, save, delete, które pracują z POJOs zawierających wszystkie pola . Wymusza to albo aktualizację dokumentów w kilku krokach (1. finddokument do aktualizacji, 2. modyfikacja odpowiednich pól ze zwróconego POJO, a następnie 3. saveto), albo ręczne zdefiniowanie własnych zapytań aktualizacyjnych @Query.

W środowisku wielowątkowym, takim jak np. Zaplecze Java z kilkoma punktami końcowymi REST, najlepszym rozwiązaniem są aktualizacje przy użyciu jednej metody, aby zmniejszyć prawdopodobieństwo, że dwie równoległe aktualizacje nadpisują nawzajem zmiany.

Przykład: mając taki dokument: { _id: "ID1", field1: "a string", field2: 10.0 }i dwa różne wątki jednocześnie go aktualizujące ...

Dzięki MongoTemplatetemu wyglądałoby to mniej więcej tak:

THREAD_001                                                      THREAD_002
|                                                               |
|update(query("ID1"), Update().set("field1", "another string")) |update(query("ID1"), Update().inc("field2", 5))
|                                                               |
|                                                               |

a ostatecznym stanem dokumentu jest zawsze, { _id: "ID1", field1: "another string", field2: 15.0 }ponieważ każdy wątek uzyskuje dostęp do bazy danych tylko raz i tylko określone pole jest zmieniane.

Podczas gdy ten sam scenariusz z MongoRepositorywyglądałby tak:

THREAD_001                                                      THREAD_002
|                                                               |
|pojo = findById("ID1")                                         |pojo = findById("ID1")
|pojo.setField1("another string") /* field2 still 10.0 */       |pojo.setField2(pojo.getField2()+5) /* field1 still "a string" */
|save(pojo)                                                     |save(pojo)
|                                                               |
|                                                               |

a końcowy dokument jest albo { _id: "ID1", field1: "another string", field2: 10.0 }czy { _id: "ID1", field1: "a string", field2: 15.0 }w zależności od saveoperacji uderza DB ostatni.
(UWAGA: Nawet gdybyśmy użyli adnotacji Spring Data,@Version jak sugerowano w komentarzach, niewiele by się zmieniło: jedna z saveoperacji wyrzuciłaby OptimisticLockingFailureException, a dokument końcowy byłby nadal jednym z powyższych, z zaktualizowanym tylko jednym polem zamiast obu. )

Więc powiedziałbym, że MongoTemplatejest to lepsza opcja , chyba że masz bardzo rozbudowany model POJO lub z MongoRepositoryjakiegoś powodu potrzebujesz niestandardowych możliwości zapytań .

walen
źródło
Zalety / przykłady. Jednak przykład sytuacji wyścigu i niepożądanego wyniku można uniknąć za pomocą @Version, aby zapobiec temu samemu scenariuszowi.
Madbreaks
@Madbreaks Czy możesz udostępnić jakieś zasoby, jak to osiągnąć? Pewnie jakiś oficjalny doktor?
Karthikeyan
Spring data docs about @Version annotation: docs.spring.io/spring-data/mongodb/docs/current/reference/html/…
Karim Tawfik
1
@Madbreaks Dzięki za wskazanie tego. Tak, @Versionczy „uniknie” drugiego wątku nadpisującego dane zapisane przez pierwszy - „uniknie” w tym sensie, że odrzuci aktualizację i OptimisticLockingFailureExceptionzamiast tego wyrzuci . Więc jeśli chcesz, aby aktualizacja się powiodła, musisz zaimplementować mechanizm ponawiania. MongoTemplate pozwala ominąć cały scenariusz.
walen