¿Cuál es la diferencia entre MongoTemplate y MongoRepository de Spring Data?

98

Necesito escribir una aplicación con la que pueda hacer consultas complejas usando spring-data y mongodb. Empecé usando MongoRepository, pero luché con consultas complejas para encontrar ejemplos o para comprender realmente la sintaxis.

Estoy hablando de consultas como esta:

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

o el uso de consultas basadas en JSON que probé por ensayo y error porque no entiendo la sintaxis correcta. Incluso después de leer la documentación de mongodb (ejemplo que no funciona debido a una sintaxis incorrecta).

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

Después de leer toda la documentación, parece que mongoTemplateestá mucho mejor documentada MongoRepository. Me refiero a la siguiente documentación:

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

¿Puede decirme qué es más conveniente y poderoso de usar? mongoTemplateo MongoRepository? ¿Ambos son iguales o uno de ellos carece de más características que el otro?

Christopher Armstrong
fuente

Respuestas:

130

"Conveniente" y "poderoso de usar" son objetivos contradictorios hasta cierto punto. Los repositorios son mucho más convenientes que las plantillas, pero estas últimas, por supuesto, le brindan un control más detallado sobre qué ejecutar.

Como el modelo de programación del repositorio está disponible para varios módulos de Spring Data, encontrará documentación más detallada en la sección general de los documentos de referencia de Spring Data MongoDB .

TL; DR

Generalmente recomendamos el siguiente enfoque:

  1. Comience con el resumen del repositorio y simplemente declare consultas simples utilizando el mecanismo de derivación de consultas o consultas definidas manualmente.
  2. Para consultas más complejas, agregue métodos implementados manualmente al repositorio (como se documenta aquí). Para el uso de implementación MongoTemplate.

Detalles

Para su ejemplo, esto se vería así:

  1. Defina una interfaz para su código personalizado:

    interface CustomUserRepository {
    
      List<User> yourCustomMethod();
    }
  2. Agregue una implementación para esta clase y siga la convención de nomenclatura para asegurarse de que podamos encontrar la clase.

    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. Ahora deje que su interfaz de repositorio base amplíe la personalizada y la infraestructura utilizará automáticamente su implementación personalizada:

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

De esta manera, básicamente, tiene la opción: todo lo que simplemente es fácil de declarar entra UserRepository, todo lo que está mejor implementado manualmente entra CustomUserRepository. Las opciones de personalización se documentan aquí .

Oliver Drotbohm
fuente
1
Hola Oliver, esto en realidad no funciona. spring-data intenta generar automáticamente una consulta a partir del nombre personalizado. yourCustomMethod (). Dirá que "su" no es un campo válido en la clase de dominio. Seguí el manual y también verifiqué dos veces cómo lo está haciendo en spring-data-jpa-examples. Sin suerte. spring-data siempre intenta autogenerarse tan pronto como extiendo la interfaz personalizada a la clase de repositorio. La única diferencia es que estoy usando MongoRepository y no CrudRepository ya que no quiero trabajar con iteradores por ahora. Si tiene una pista, se lo agradecería.
Christopher Armstrong
11
El error más común es el nombre de la clase de implementación incorrecta: si su interfaz de recompra de base se llama YourRepository, la clase de implementación tiene que ser identificado YourRepositoryImpl. ¿Es ese el caso? Si es así, estoy feliz de echar un vistazo a un proyecto de muestra en GitHub o similar…
Oliver Drotbohm
5
Hola Oliver, el nombre de la clase Impl fue incorrecto como has asumido. Ajusté el nombre y parece que está funcionando ahora. Muchas gracias por tus comentarios. Es realmente genial poder usar diferentes tipos de opciones de consulta de esta manera. ¡Bien pensado!
Christopher Armstrong
Esta respuesta no es tan clara. Después de hacer todo con este ejemplo, caigo en este problema: stackoverflow.com/a/13947263/449553 . Entonces, la convención de nomenclatura es más estricta de lo que parece en este ejemplo.
msangel
1
La clase de implementación en el n. ° 2 tiene un nombre incorrecto: debería ser CustomUserRepositoryy no CustomerUserRepository.
Cotta
28

Esta respuesta puede retrasarse un poco, pero recomendaría evitar toda la ruta del repositorio. Obtiene muy pocos métodos implementados de gran valor práctico. Para que funcione, se encuentra con la tontería de la configuración de Java en la que puede pasar días y semanas sin mucha ayuda en la documentación.

En su lugar, siga la MongoTemplateruta y cree su propia capa de acceso a datos que lo libere de las pesadillas de configuración que enfrentan los programadores de Spring. MongoTemplatees realmente el salvador para los ingenieros que se sienten cómodos diseñando sus propias clases e interacciones, ya que hay mucha flexibilidad. La estructura puede ser algo como esto:

  1. Crea una MongoClientFactoryclase que se ejecutará a nivel de aplicación y te dará un MongoClientobjeto. Puede implementar esto como Singleton o usando un Enum Singleton (esto es seguro para subprocesos)
  2. Cree una clase base de acceso a datos de la que pueda heredar un objeto de acceso a datos para cada objeto de dominio). La clase base puede implementar un método para crear un objeto MongoTemplate que los métodos específicos de su clase pueden usar para todos los accesos a la base de datos
  3. Cada clase de acceso a datos para cada objeto de dominio puede implementar los métodos básicos o puede implementarlos en la clase base
  4. Los métodos del controlador pueden llamar a métodos en las clases de acceso a datos según sea necesario.
rameshpa
fuente
Hola @rameshpa ¿Puedo usar MongoTemplate y el repositorio en el mismo proyecto? .. ¿Es posible usarlo?
Gauranga
1
Podría, pero el MongoTemplate que implemente tendrá una conexión diferente a la base de datos que la conexión utilizada por Repository. La atomicidad podría ser un problema. Además, no recomendaría usar dos conexiones diferentes en un hilo si tiene necesidades de secuenciación
rameshpa
24

FWIW, con respecto a las actualizaciones en un entorno de subprocesos múltiples:

  • MongoTemplateproporciona "atómica" out-of-the-box operaciones updateFirst , updateMulti, findAndModify, upsert..., que le permiten modificar un documento en una sola operación. El Updateobjeto utilizado por estos métodos también le permite apuntar solo a los campos relevantes .
  • MongoRepositorysólo le da las operaciones básicas CRUD find , insert, save, delete, que trabajan con POJOs que contienen todos los campos . Esto le obliga a actualizar los documentos en varios pasos (1. findel documento a actualizar, 2. modificar los campos relevantes del POJO devuelto, y luego 3. save), o definir sus propias consultas de actualización manualmente usando @Query.

En un entorno de subprocesos múltiples, como por ejemplo, un back-end de Java con varios puntos finales REST, las actualizaciones de un solo método son el camino a seguir, para reducir las posibilidades de que dos actualizaciones simultáneas sobrescriban los cambios entre sí.

Ejemplo: dado un documento como este: { _id: "ID1", field1: "a string", field2: 10.0 }y dos subprocesos diferentes actualizándolo simultáneamente ...

Con MongoTemplatese vería algo así:

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

y el estado final del documento es siempre, { _id: "ID1", field1: "another string", field2: 15.0 }ya que cada hilo accede a la base de datos solo una vez y solo se cambia el campo especificado.

Considerando que el mismo escenario de caso con MongoRepositoryse vería así:

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

y el documento final es { _id: "ID1", field1: "another string", field2: 10.0 }o { _id: "ID1", field1: "a string", field2: 15.0 }depende de savela última operación que llegue a la base de datos.
(NOTA: Incluso si usáramos la @Versionanotación de Spring Data como se sugiere en los comentarios, no cambiaría mucho: una de las saveoperaciones arrojaría un OptimisticLockingFailureException, y el documento final aún sería uno de los anteriores, con solo un campo actualizado en lugar de ambos. )

Entonces, diría que MongoTemplatees una mejor opción , a menos que tenga un modelo POJO muy elaborado o necesite las capacidades de consultas personalizadas de MongoRepositorypor alguna razón.

walen
fuente
Buenos puntos / ejemplos. Sin embargo, su ejemplo de condición de carrera y el resultado no deseado se pueden evitar usando @Version para evitar ese mismo escenario.
Madbreaks
@Madbreaks ¿Puede proporcionar algún recurso sobre cómo lograr esto? ¿Algún documento oficial probablemente?
Karthikeyan
Documentos de datos de primavera sobre la anotación @Version: docs.spring.io/spring-data/mongodb/docs/current/reference/html/…
Karim Tawfik
1
@Madbreaks Gracias por señalar eso. Sí, @Version"evitaría" que el segundo subproceso sobrescribiera los datos guardados por el primero - "evitar" en el sentido de que descartaría la actualización y lanzaría una OptimisticLockingFailureException. Por lo tanto, tendría que implementar un mecanismo de reintento si desea que la actualización se realice correctamente. MongoTemplate le permite evitar todo el escenario.
Gales