Quelle est la différence entre MongoTemplate de Spring Data et MongoRepository?

97

J'ai besoin d'écrire une application avec laquelle je peux faire des requêtes complexes en utilisant spring-data et mongodb. J'ai commencé par utiliser MongoRepository mais j'ai eu du mal avec des requêtes complexes pour trouver des exemples ou pour comprendre réellement la syntaxe.

Je parle de requêtes comme celle-ci:

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

ou l'utilisation de requêtes basées sur JSON que j'ai essayées par essais et erreurs parce que je ne comprends pas la bonne syntaxe. Même après avoir lu la documentation de mongodb (exemple qui ne fonctionne pas en raison d'une syntaxe incorrecte).

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

Après avoir lu toute la documentation, il semble que ce mongoTemplatesoit bien mieux documenté MongoRepository. Je fais référence à la documentation suivante:

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

Pouvez-vous me dire ce qui est plus pratique et plus puissant à utiliser? mongoTemplateou MongoRepository? Les deux sont-ils les mêmes matures ou l'un d'eux manque-t-il plus de fonctionnalités que l'autre?

Christopher Armstrong
la source

Réponses:

130

«Pratique» et «puissant à utiliser» sont en contradiction avec les objectifs dans une certaine mesure. Les référentiels sont de loin plus pratiques que les modèles, mais ces derniers vous donnent bien sûr un contrôle plus fin sur ce qu'il faut exécuter.

Comme le modèle de programmation du référentiel est disponible pour plusieurs modules Spring Data, vous trouverez une documentation plus détaillée à ce sujet dans la section générale de la documentation de référence Spring Data MongoDB .

TL; DR

Nous recommandons généralement l'approche suivante:

  1. Commencez par le résumé du référentiel et déclarez simplement des requêtes simples à l'aide du mécanisme de dérivation de requêtes ou des requêtes définies manuellement.
  2. Pour les requêtes plus complexes, ajoutez des méthodes implémentées manuellement au référentiel (comme documenté ici). Pour la mise en œuvre, utilisez MongoTemplate.

Détails

Pour votre exemple, cela ressemblerait à ceci:

  1. Définissez une interface pour votre code personnalisé:

    interface CustomUserRepository {
    
      List<User> yourCustomMethod();
    }
  2. Ajoutez une implémentation pour cette classe et suivez la convention de dénomination pour vous assurer que nous pouvons trouver 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. Maintenant, laissez votre interface de référentiel de base étendre l'interface personnalisée et l'infrastructure utilisera automatiquement votre implémentation personnalisée:

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

De cette façon, vous avez essentiellement le choix: tout ce qui est simplement facile à déclarer entre dans UserRepository, tout ce qui est mieux implémenté manuellement CustomUserRepository. Les options de personnalisation sont documentées ici .

Oliver Drotbohm
la source
Salut Oliver, ça ne marche pas. spring-data essaie de générer automatiquement une requête à partir du nom personnalisé. yourCustomMethod (). Il indiquera que "votre" n'est pas un champ valide dans la classe de domaine. J'ai suivi le manuel et j'ai également vérifié comment vous le faites avec spring-data-jpa-examples. Pas de chance. spring-data essaie toujours de se générer automatiquement dès que j'étends l'interface personnalisée à la classe du référentiel. La seule différence est que j'utilise MongoRepository et non CrudRepository car je ne veux pas travailler avec les itérateurs pour le moment. Si vous aviez un indice, ce serait apprécié.
Christopher Armstrong
10
L'erreur la plus courante est de mal nommer la classe d'implémentation: si votre interface de référentiel de base est appelée YourRepository, la classe d'implémentation doit être nommée YourRepositoryImpl. Est-ce le cas? Si c'est le cas, je suis heureux de jeter un coup d'œil à un exemple de projet sur GitHub ou similaire…
Oliver Drotbohm
5
Salut Oliver, la classe Impl a été mal nommée comme vous l'avez supposé. J'ai ajusté le nom et il semble que cela fonctionne maintenant. Merci beaucoup pour vos commentaires. C'est vraiment cool de pouvoir utiliser différents types d'options de requête de cette façon. Bien pensé!
Christopher Armstrong
Cette réponse n'est pas si claire. Après avoir tout fait par cet exemple, je tombe sur ce problème: stackoverflow.com/a/13947263/449553 . La convention de dénomination est donc plus stricte qu'elle ne le semble dans cet exemple.
msangel
1
La classe d'implémentation sur # 2 est mal nommée: devrait l'être CustomUserRepositoryet non CustomerUserRepository.
Cotta
28

Cette réponse peut être un peu retardée, mais je recommanderais d'éviter toute la route du référentiel. Vous obtenez très peu de méthodes implémentées d'une grande valeur pratique. Pour que cela fonctionne, vous rencontrez des absurdités de configuration Java sur lesquelles vous pouvez passer des jours et des semaines sans beaucoup d'aide dans la documentation.

Au lieu de cela, suivez l' MongoTemplateitinéraire et créez votre propre couche d'accès aux données qui vous libère des cauchemars de configuration auxquels sont confrontés les programmeurs Spring. MongoTemplateest vraiment le sauveur pour les ingénieurs qui sont à l'aise pour concevoir leurs propres classes et interactions car il y a beaucoup de flexibilité. La structure peut être quelque chose comme ceci:

  1. Créez une MongoClientFactoryclasse qui s'exécutera au niveau de l'application et vous donnera un MongoClientobjet. Vous pouvez l'implémenter en tant que Singleton ou en utilisant un Enum Singleton (c'est thread-safe)
  2. Créez une classe de base d'accès aux données à partir de laquelle vous pouvez hériter d'un objet d'accès aux données pour chaque objet de domaine). La classe de base peut implémenter une méthode pour créer un objet MongoTemplate que les méthodes spécifiques de votre classe peuvent utiliser pour tous les accès à la base de données
  3. Chaque classe d'accès aux données pour chaque objet de domaine peut implémenter les méthodes de base ou vous pouvez les implémenter dans la classe de base
  4. Les méthodes Controller peuvent ensuite appeler des méthodes dans les classes d'accès aux données selon les besoins.
rameshpa
la source
Salut @rameshpa Puis-je utiliser à la fois MongoTemplate et le référentiel dans le même projet? .. Est-il possible d'utiliser
Gauranga
1
Vous pourriez mais le MongoTemplate que vous implémentez aura une connexion à la base de données différente de celle utilisée par Repository. L'atomicité pourrait être un problème. De plus, je ne recommanderais pas d'utiliser deux connexions différentes sur un même thread si vous avez des besoins de séquençage
rameshpa
24

FWIW, concernant les mises à jour dans un environnement multi-thread:

  • MongoTemplatefournit des opérations « atomique » out-of-the-box updateFirst , updateMulti, findAndModify, upsert... qui vous permettent de modifier un document en une seule opération. L' Updateobjet utilisé par ces méthodes vous permet également de cibler uniquement les champs pertinents .
  • MongoRepositoryne vous donne les opérations CRUD de base find , insert, save, delete, qui travaillent avec POJO contenant tous les champs . Cela vous oblige soit à mettre à jour les documents en plusieurs étapes (1. findle document à mettre à jour, 2. à modifier les champs pertinents du POJO renvoyé, puis 3. à savele), soit à définir manuellement vos propres requêtes de mise à jour à l'aide de @Query.

Dans un environnement multi-thread, comme par exemple un back-end Java avec plusieurs points de terminaison REST, les mises à jour à méthode unique sont la voie à suivre, afin de réduire les chances que deux mises à jour simultanées écrasent les modifications de l'autre.

Exemple: étant donné un document comme celui-ci: { _id: "ID1", field1: "a string", field2: 10.0 }et deux threads différents le mettant à jour simultanément ...

Avec MongoTemplatecela ressemblerait un peu à ceci:

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

et l'état final du document est toujours { _id: "ID1", field1: "another string", field2: 15.0 }puisque chaque thread n'accède au DB qu'une seule fois et que seul le champ spécifié est modifié.

Alors que le même scénario de cas MongoRepositoryressemblerait à ceci:

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

et le document final étant l'un { _id: "ID1", field1: "another string", field2: 10.0 }ou l' autre ou { _id: "ID1", field1: "a string", field2: 15.0 }selon quelle saveopération atteint le dernier DB.
(REMARQUE: même si nous utilisions l' @Versionannotation de Spring Data comme suggéré dans les commentaires, rien ne changerait: l'une des saveopérations lèverait un OptimisticLockingFailureException, et le document final serait toujours l'un des ci-dessus, avec un seul champ mis à jour au lieu des deux. )

Je dirais donc que MongoTemplatec'est une meilleure option , à moins que vous n'ayez un modèle POJO très élaboré ou que vous ayez besoin des capacités de requêtes personnalisées de MongoRepositorypour une raison quelconque.

Walen
la source
Bons points / exemples. Cependant, votre exemple de condition de concurrence et le résultat indésirable peuvent être évités en utilisant @Version pour empêcher ce scénario même.
Madbreaks
@Madbreaks Pouvez-vous fournir des ressources sur la façon d'y parvenir? Un doc officiel probablement?
Karthikeyan
Documents de
Karim Tawfik
1
@Madbreaks Merci de l'avoir signalé. Oui, @Version"éviterait" le deuxième thread d'écraser les données sauvegardées par le premier - "éviterait" dans le sens où il rejetterait la mise à jour et lancerait un à la OptimisticLockingFailureExceptionplace. Vous devrez donc implémenter un mécanisme de nouvelle tentative si vous voulez que la mise à jour réussisse. MongoTemplate vous permet d'éviter tout le scénario.
walen le