Qual è la differenza tra MongoTemplate di Spring Data e MongoRepository?


98

Ho bisogno di scrivere un'applicazione con la quale posso fare query complesse usando spring-data e mongodb. Ho iniziato a utilizzare MongoRepository ma ho lottato con query complesse per trovare esempi o per comprendere effettivamente la sintassi.

Sto parlando di query come questa:

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

o l'uso di query basate su JSON che ho provato per tentativi ed errori perché non ho la sintassi corretta. Anche dopo aver letto la documentazione di mongodb (esempio non funzionante a causa di una sintassi errata).

@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);
} 

Dopo aver letto tutta la documentazione, sembra che mongoTemplatesia molto meglio documentata allora MongoRepository. Mi riferisco alla seguente documentazione:

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

Puoi dirmi cosa è più comodo e potente da usare? mongoTemplateo MongoRepository? Sono entrambi maturi o uno di loro manca di più caratteristiche dell'altro?

Risposte:


130

"Comodo" e "potente da usare" sono obiettivi in ​​una certa misura contraddittori. I repository sono di gran lunga più convenienti dei modelli, ma questi ultimi ovviamente ti danno un controllo più dettagliato su cosa eseguire.

Poiché il modello di programmazione del repository è disponibile per più moduli Spring Data, troverai una documentazione più approfondita nella sezione generale dei documenti di riferimento di Spring Data MongoDB .

TL; DR

In genere consigliamo il seguente approccio:

  1. Inizia con l'abstract del repository e dichiara semplicemente query semplici utilizzando il meccanismo di derivazione delle query o query definite manualmente.
  2. Per query più complesse, aggiungi metodi implementati manualmente al repository (come documentato qui). Per l'uso implementativo MongoTemplate.

Dettagli

Per il tuo esempio questo sarebbe simile a questo:

  1. Definisci un'interfaccia per il tuo codice personalizzato:

    interface CustomUserRepository {
    
      List<User> yourCustomMethod();
    }
  2. Aggiungi un'implementazione per questa classe e segui la convenzione di denominazione per assicurarti di poter trovare la classe.

    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. Ora lascia che la tua interfaccia del repository di base estenda quella personalizzata e l'infrastruttura utilizzerà automaticamente la tua implementazione personalizzata:

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

In questo modo essenzialmente hai la scelta: tutto ciò che è facile da dichiarare va dentro UserRepository, tutto ciò che è meglio implementato manualmente va dentro CustomUserRepository. Le opzioni di personalizzazione sono documentate qui .


1
Ciao Oliver, questo in realtà non funziona. spring-data tenta di generare automaticamente una query dal nome personalizzato. yourCustomMethod (). Dirà che "tuo" non è un campo valido nella classe di dominio. Ho seguito il manuale e ho anche ricontrollato come lo stai facendo negli esempi di spring-data-jpa. Senza fortuna. spring-data cerca sempre di auto-generare non appena estendo l'interfaccia personalizzata alla classe del repository. L'unica differenza è che sto usando MongoRepository e non CrudRepository perché per ora non voglio lavorare con Iterator. Se avessi un suggerimento, sarebbe apprezzato.
Christopher Armstrong

11
L'errore più comune è dare un nome sbagliato alla classe di implementazione: se viene chiamata YourRepositoryl' interfaccia del repository di base , la classe di implementazione deve essere nominata YourRepositoryImpl. È così? Se è così, sono felice di dare un'occhiata a un progetto di esempio su GitHub o simili ...
Oliver Drotbohm

5
Ciao Oliver, la classe Impl è stata nominata sbagliata come hai supposto. Ho modificato il nome e ora sembra che funzioni. Grazie mille per il tuo feedback. È davvero interessante poter utilizzare diversi tipi di opzioni di query in questo modo. Ben pensato!
Christopher Armstrong

Questa risposta non è così chiara. Dopo aver fatto tutto con questo esempio, cado in questo problema: stackoverflow.com/a/13947263/449553 . Quindi la convenzione di denominazione è più rigida di quanto sembri da questo esempio.
msangel

1
La classe di implementazione su # 2 è denominata errata: dovrebbe essere CustomUserRepositorye non CustomerUserRepository.
Cotta

28

Questa risposta potrebbe essere un po 'ritardata, ma consiglierei di evitare l'intero percorso del repository. Ottieni pochissimi metodi implementati di grande valore pratico. Per farlo funzionare ti imbatti nelle sciocchezze della configurazione di Java su cui puoi passare giorni e settimane senza molto aiuto nella documentazione.

Invece, segui il MongoTemplatepercorso e crea il tuo livello di accesso ai dati che ti libera dagli incubi di configurazione affrontati dai programmatori di Spring. MongoTemplateè davvero il salvatore per gli ingegneri che si sentono a proprio agio nell'architettura delle proprie classi e interazioni poiché c'è molta flessibilità. La struttura può essere qualcosa del genere:

  1. Crea una MongoClientFactoryclasse che verrà eseguita a livello di applicazione e ti fornirà un MongoClientoggetto. Puoi implementarlo come Singleton o usando un Enum Singleton (questo è thread-safe)
  2. Creare una classe base di accesso ai dati da cui è possibile ereditare un oggetto di accesso ai dati per ogni oggetto di dominio). La classe base può implementare un metodo per creare un oggetto MongoTemplate che i metodi specifici della tua classe possono utilizzare per tutti gli accessi al DB
  3. Ogni classe di accesso ai dati per ogni oggetto di dominio può implementare i metodi di base oppure è possibile implementarli nella classe di base
  4. I metodi del controller possono quindi chiamare i metodi nelle classi di accesso ai dati secondo necessità.

Ciao @rameshpa Posso usare MongoTemplate e repository nello stesso progetto? .. È possibile usare
Gauranga

1
Potresti ma il MongoTemplate che implementi avrà una connessione diversa al DB rispetto alla connessione utilizzata dal repository. L'atomicità potrebbe essere un problema. Inoltre, non consiglierei di utilizzare due connessioni diverse su un thread se hai esigenze di sequenziamento
rameshpa

24

FWIW, per quanto riguarda gli aggiornamenti in un ambiente multi-thread:

  • MongoTemplatefornisce "atomica" out-of-the-box operazioni updateFirst , updateMulti, findAndModify, upsert..., che consentono di modificare un documento in una sola operazione. L' Updateoggetto utilizzato da questi metodi consente inoltre di indirizzare solo i campi pertinenti .
  • MongoRepositorysolo ti offre l'operazioni CRUD di base find , insert, save, delete, che funzionano con POJO che contengono tutti i campi . Questo ti obbliga ad aggiornare i documenti in diversi passaggi (1. findil documento da aggiornare, 2. modificare i campi rilevanti dal POJO restituito, e poi 3. save), o definire le tue query di aggiornamento manualmente usando @Query.

In un ambiente multi-thread, come ad esempio un back-end Java con diversi endpoint REST, gli aggiornamenti a metodo singolo sono la strada da percorrere, al fine di ridurre le possibilità che due aggiornamenti simultanei si sovrascrivano a vicenda.

Esempio: dato un documento come questo: { _id: "ID1", field1: "a string", field2: 10.0 }e due diversi thread che lo aggiornano contemporaneamente ...

Con MongoTemplateesso sarebbe simile a questo:

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

e lo stato finale del documento è sempre { _id: "ID1", field1: "another string", field2: 15.0 }poiché ogni thread accede al DB solo una volta e viene modificato solo il campo specificato.

Considerando che lo stesso scenario del caso con MongoRepositorysarebbe simile a questo:

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)
|                                                               |
|                                                               |

e il documento finale è { _id: "ID1", field1: "another string", field2: 10.0 }o { _id: "ID1", field1: "a string", field2: 15.0 }dipende da quale saveoperazione colpisce per ultima il DB.
(NOTA: anche se usassimo l' @Versionannotazione di Spring Data come suggerito nei commenti, non cambierebbe molto: una delle saveoperazioni genererebbe un OptimisticLockingFailureExceptione il documento finale sarebbe comunque uno dei precedenti, con un solo campo aggiornato invece di entrambi. )

Quindi direi che MongoTemplateè un'opzione migliore , a meno che tu non abbia un modello POJO molto elaborato o che tu abbia bisogno delle funzionalità di query personalizzate MongoRepositoryper qualche motivo.


Buoni punti / esempi. Tuttavia, il tuo esempio di condizione di gara e il risultato indesiderato possono essere evitati usando @Version per prevenire quello stesso scenario.
Madbreaks

@ Madbreaks Potete fornire risorse su come raggiungere questo obiettivo? Qualche documento ufficiale probabilmente?
Karthikeyan

Documenti di dati primaverili sull'annotazione @Version: docs.spring.io/spring-data/mongodb/docs/current/reference/html/…
Karim Tawfik

1
@ Madbreaks Grazie per averlo fatto notare. Sì, @Version"eviterebbe" che il secondo thread sovrascriva i dati salvati dal primo - "eviterebbe" nel senso che scarterebbe l'aggiornamento e ne lancerebbe uno OptimisticLockingFailureException. Quindi dovresti implementare un meccanismo di ripetizione se vuoi che l'aggiornamento abbia successo. MongoTemplate ti consente di evitare l'intero scenario.
Walen
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.