Was ist der Unterschied zwischen MongoTemplate und MongoRepository von Spring Data?

96

Ich muss eine Anwendung schreiben, mit der ich komplexe Abfragen mit Spring-Data und Mongodb durchführen kann. Ich habe mit der Verwendung des MongoRepository begonnen, hatte jedoch Probleme mit komplexen Abfragen, um Beispiele zu finden oder die Syntax tatsächlich zu verstehen.

Ich spreche von Fragen wie diesen:

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

oder die Verwendung von JSON-basierten Abfragen, die ich durch Ausprobieren ausprobiert habe, weil ich die Syntax nicht richtig verstanden habe. Auch nach dem Lesen der Mongodb-Dokumentation (nicht funktionierendes Beispiel aufgrund falscher Syntax).

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

Nach dem Durchlesen der gesamten Dokumentation scheint dies mongoTemplateweitaus besser dokumentiert zu sein MongoRepository. Ich beziehe mich auf folgende Dokumentation:

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

Können Sie mir sagen, was bequemer und leistungsfähiger zu verwenden ist? mongoTemplateoder MongoRepository? Sind beide gleich ausgereift oder fehlt einem von ihnen mehr Funktionen als dem anderen?

Christopher Armstrong
quelle

Antworten:

130

"Praktisch" und "leistungsstark zu bedienen" widersprechen in gewissem Maße den Zielen. Repositorys sind weitaus praktischer als Vorlagen, aber letztere bieten Ihnen natürlich eine genauere Kontrolle darüber, was ausgeführt werden soll.

Da das Repository-Programmiermodell für mehrere Spring Data-Module verfügbar ist, finden Sie eine ausführlichere Dokumentation im allgemeinen Abschnitt der Spring Data MongoDB- Referenzdokumente .

TL; DR

Wir empfehlen generell den folgenden Ansatz:

  1. Beginnen Sie mit dem Repository-Abstract und deklarieren Sie einfach einfache Abfragen mithilfe des Ableitungsableitungsmechanismus oder manuell definierter Abfragen.
  2. Fügen Sie bei komplexeren Abfragen manuell implementierte Methoden zum Repository hinzu (wie hier dokumentiert). Für die Implementierung verwenden MongoTemplate.

Einzelheiten

Für Ihr Beispiel würde dies ungefähr so ​​aussehen:

  1. Definieren Sie eine Schnittstelle für Ihren benutzerdefinierten Code:

    interface CustomUserRepository {
    
      List<User> yourCustomMethod();
    }
    
  2. Fügen Sie eine Implementierung für diese Klasse hinzu und befolgen Sie die Namenskonvention, um sicherzustellen, dass wir die Klasse finden können.

    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. Lassen Sie nun Ihre Basis-Repository-Schnittstelle die benutzerdefinierte erweitern, und die Infrastruktur verwendet automatisch Ihre benutzerdefinierte Implementierung:

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

Auf diese Weise haben Sie im Wesentlichen die Wahl: Alles, was einfach zu deklarieren ist, geht in UserRepositoryalles, was besser manuell implementiert wird CustomUserRepository. Die Anpassungsoptionen sind hier dokumentiert .

Oliver Drotbohm
quelle
Hallo Oliver, das funktioniert eigentlich nicht. spring-data versucht, automatisch eine Abfrage aus dem benutzerdefinierten Namen zu generieren. yourCustomMethod (). Es wird angezeigt, dass "Ihr" kein gültiges Feld in der Domänenklasse ist. Ich habe das Handbuch befolgt und auch überprüft, wie Sie es machen, die spring-data-jpa-Beispiele. Kein Glück. spring-data versucht immer automatisch zu generieren, sobald ich die benutzerdefinierte Schnittstelle auf die Repository-Klasse erweitere. Der einzige Unterschied besteht darin, dass ich MongoRepository und nicht CrudRepository verwende, da ich vorerst nicht mit Iteratoren arbeiten möchte. Wenn Sie einen Hinweis hätten, wäre er dankbar.
Christopher Armstrong
10
Der häufigste Fehler besteht darin, die Implementierungsklasse falsch zu benennen: Wenn Ihre Basis-Repo-Schnittstelle aufgerufen wird YourRepository, muss die Implementierungsklasse benannt werden YourRepositoryImpl. Ist das der Fall? Wenn ja, schaue ich mir gerne ein Beispielprojekt auf GitHub oder ähnlichem an…
Oliver Drotbohm
5
Hallo Oliver, die Impl-Klasse wurde falsch benannt, wie du angenommen hast. Ich habe den Namen angepasst und es sieht so aus, als würde es jetzt funktionieren. Vielen Dank für Ihr Feedback. Es ist wirklich cool, auf diese Weise verschiedene Arten von Abfrageoptionen verwenden zu können. Gut durchdacht!
Christopher Armstrong
Diese Antwort ist nicht so klar. Nachdem ich alles anhand dieses Beispiels gemacht habe, falle ich auf dieses Problem: stackoverflow.com/a/13947263/449553 . Die Namenskonvention ist also strenger, als es in diesem Beispiel aussieht.
Msangel
1
Die Implementierungsklasse auf # 2 heißt falsch: sollte sein CustomUserRepositoryund nicht CustomerUserRepository.
Cotta
28

Diese Antwort kann etwas verzögert sein, aber ich würde empfehlen, die gesamte Repository-Route zu vermeiden. Sie erhalten sehr wenig implementierte Methoden von großem praktischem Wert. Damit es funktioniert, stoßen Sie auf den Unsinn der Java-Konfiguration, an dem Sie Tage und Wochen ohne viel Hilfe in der Dokumentation arbeiten können.

Gehen Sie stattdessen zur MongoTemplateRoute und erstellen Sie Ihre eigene Datenzugriffsschicht, die Sie von den Konfigurations-Albträumen befreit, mit denen Spring-Programmierer konfrontiert sind. MongoTemplateist wirklich der Retter für Ingenieure, die ihre eigenen Klassen und Interaktionen gut gestalten können, da es viel Flexibilität gibt. Die Struktur kann ungefähr so ​​aussehen:

  1. Erstellen Sie eine MongoClientFactoryKlasse, die auf Anwendungsebene ausgeführt wird, und geben Sie ein MongoClientObjekt an. Sie können dies als Singleton oder mit einem Enum Singleton implementieren (dies ist threadsicher).
  2. Erstellen Sie eine Datenzugriffsbasisklasse, von der Sie für jedes Domänenobjekt ein Datenzugriffsobjekt erben können. Die Basisklasse kann eine Methode zum Erstellen eines MongoTemplate-Objekts implementieren, die Sie klassenspezifischen Methoden für alle DB-Zugriffe verwenden können
  3. Jede Datenzugriffsklasse für jedes Domänenobjekt kann die grundlegenden Methoden implementieren oder Sie können sie in der Basisklasse implementieren
  4. Die Controller-Methoden können dann nach Bedarf Methoden in den Datenzugriffsklassen aufrufen.
rameshpa
quelle
Hallo @rameshpa Kann ich sowohl MongoTemplate als auch Repository in demselben Projekt verwenden? .. Ist es möglich zu verwenden
Gauranga
1
Sie könnten aber das von Ihnen implementierte MongoTemplate eine andere Verbindung zur Datenbank haben als die vom Repository verwendete Verbindung. Atomarität könnte ein Problem sein. Außerdem würde ich nicht empfehlen, zwei verschiedene Verbindungen auf einem Thread zu verwenden, wenn Sie Sequenzierungsanforderungen haben
rameshpa
22

FWIW zu Updates in einer Multithread-Umgebung:

  • MongoTemplatebietet „atomare“ out-of-the-Box - Operationen updateFirst , updateMulti, findAndModify, upsert... , das Ihnen ein Dokument in einem einzigen Vorgang modifizieren lassen. Mit dem Updatevon diesen Methoden verwendeten Objekt können Sie auch nur auf die relevanten Felder abzielen .
  • MongoRepositorynur gibt Ihnen die grundlegenden CRUD - Operationen find , insert, save, delete, die Arbeit mit POJOs enthält alle Felder . Dies zwingt Sie, entweder die Dokumente in mehreren Schritten findzu aktualisieren (1. das zu aktualisierende Dokument, 2. die relevanten Felder aus dem zurückgegebenen POJO zu ändern und dann 3. savees) oder Ihre eigenen Aktualisierungsabfragen von Hand mit zu definieren @Query.

In einer Multithread-Umgebung, z. B. einem Java-Back-End mit mehreren REST-Endpunkten, sind Aktualisierungen mit einer Methode der richtige Weg, um die Wahrscheinlichkeit zu verringern, dass zwei Aktualisierungen gleichzeitig die Änderungen des anderen überschreiben.

Beispiel: ein Dokument wie dieses gegeben: { _id: "ID1", field1: "a string", field2: 10.0 }und zwei verschiedene Threads, die es gleichzeitig aktualisieren ...

Damit MongoTemplatewürde es ungefähr so ​​aussehen:

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

und der Endzustand für das Dokument ist immer, { _id: "ID1", field1: "another string", field2: 15.0 }da jeder Thread nur einmal auf die Datenbank zugreift und nur das angegebene Feld geändert wird.

Während das gleiche Szenario mit MongoRepositoryso aussehen würde:

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

und das endgültige Dokument ist entweder { _id: "ID1", field1: "another string", field2: 10.0 }oder { _id: "ID1", field1: "a string", field2: 15.0 }abhängig davon, welche saveOperation die Datenbank zuletzt trifft.
(HINWEIS: Selbst wenn wir die in den Kommentaren vorgeschlagene Annotation von Spring Data@Version verwenden würden, würde sich nicht viel ändern: Eine der saveOperationen würde eine auslösen OptimisticLockingFailureException, und das endgültige Dokument wäre immer noch eine der oben genannten, wobei nur ein Feld anstelle von beiden aktualisiert wird. )

Daher würde ich sagen, dass dies MongoTemplateeine bessere Option ist , es sei denn, Sie haben ein sehr ausgefeiltes POJO-Modell oder benötigen MongoRepositoryaus irgendeinem Grund die Funktionen für benutzerdefinierte Abfragen .

walen
quelle
Gute Punkte / Beispiele. Ihr Beispiel für eine Rennbedingung und Ihr unerwünschtes Ergebnis können jedoch mit @Version vermieden werden, um genau dieses Szenario zu verhindern.
Madbreaks
@Madbreaks Können Sie Ressourcen bereitstellen, um dies zu erreichen? Irgendein offizielles Dokument wahrscheinlich?
Karthikeyan
Frühlingsdaten-Dokumente zur @ Versions-Annotation: docs.spring.io/spring-data/mongodb/docs/current/reference/html/…
Karim Tawfik
1
@ Madbreaks Danke, dass du darauf hingewiesen hast. Ja, @Versionwürde "vermeiden", dass der zweite Thread die vom ersten gespeicherten Daten überschreibt - "vermeiden" in dem Sinne, dass das Update verworfen und OptimisticLockingFailureExceptionstattdessen ein geworfen wird. Sie müssten also einen Wiederholungsmechanismus implementieren, wenn das Update erfolgreich sein soll. Mit MongoTemplate können Sie das gesamte Szenario vermeiden.
Wal