Qual é a diferença entre MongoTemplate e MongoRepository do Spring Data?

96

Preciso escrever um aplicativo com o qual possa fazer consultas complexas usando spring-data e mongodb. Comecei usando o MongoRepository, mas lutei com consultas complexas para encontrar exemplos ou realmente entender a sintaxe.

Estou falando de consultas como esta:

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

ou o uso de consultas baseadas em JSON que tentei por tentativa e erro porque não entendi a sintaxe certa. Mesmo depois de ler a documentação do mongodb (exemplo que não funciona devido à sintaxe errada).

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

Depois de ler toda a documentação, parece que mongoTemplateestá muito melhor documentado MongoRepository. Estou me referindo à seguinte documentação:

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

Você pode me dizer o que é mais conveniente e poderoso de usar? mongoTemplateou MongoRepository? Ambos são maduros ou um deles não tem mais recursos do que o outro?

Christopher Armstrong
fonte

Respostas:

130

"Conveniente" e "poderoso para usar" são objetivos contraditórios até certo ponto. Repositórios são muito mais convenientes do que modelos, mas os últimos, é claro, oferecem um controle mais refinado sobre o que executar.

Como o modelo de programação do repositório está disponível para vários módulos do Spring Data, você encontrará uma documentação mais detalhada sobre ele na seção geral dos documentos de referência do Spring Data MongoDB .

TL; DR

Geralmente recomendamos a seguinte abordagem:

  1. Comece com o resumo do repositório e apenas declare consultas simples usando o mecanismo de derivação de consultas ou consultas definidas manualmente.
  2. Para consultas mais complexas, adicione métodos implementados manualmente ao repositório (conforme documentado aqui). Para o uso de implementação MongoTemplate.

Detalhes

Para seu exemplo, seria algo assim:

  1. Defina uma interface para o seu código personalizado:

    interface CustomUserRepository {
    
      List<User> yourCustomMethod();
    }
  2. Adicione uma implementação para esta classe e siga a convenção de nomenclatura para garantir que possamos encontrar a 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. Agora deixe sua interface de repositório base estender a interface personalizada e a infraestrutura usará automaticamente sua implementação personalizada:

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

Dessa forma, você essencialmente tem a escolha: tudo que é fácil de declarar entra UserRepository, tudo que é melhor implementado manualmente entra CustomUserRepository. As opções de personalização estão documentadas aqui .

Oliver Drotbohm
fonte
Oi Oliver, isso realmente não funciona. spring-data tenta gerar automaticamente uma consulta a partir do nome personalizado. yourCustomMethod (). Ele dirá que "seu" não é um campo válido na classe de domínio. Eu segui o manual e também verifiquei como você está fazendo os exemplos spring-data-jpa. Sem sorte. spring-data sempre tenta gerar automaticamente assim que eu estendo a interface customizada para a classe do repositório. A única diferença é que estou usando MongoRepository e não CrudRepository, já que não quero trabalhar com Iteradores por enquanto. Se você tivesse uma dica, seria apreciada.
Christopher Armstrong
10
O erro mais comum é nomear incorretamente a classe de implementação: se sua interface repo base for chamada YourRepository, a classe de implementação terá que ser nomeada YourRepositoryImpl. É esse o caso? Se sim, estou feliz em dar uma olhada em um projeto de amostra no GitHub ou algo semelhante ...
Oliver Drotbohm
5
Olá Oliver, a classe Impl foi nomeada incorretamente, como você supôs. Eu ajustei o nome e parece que está funcionando agora. Muito obrigado pelo seu feedback. É muito legal poder usar diferentes tipos de opções de consulta dessa maneira. Bem pensado!
Christopher Armstrong
Esta resposta não é tão clara. Depois de fazer tudo por este exemplo, caio neste problema: stackoverflow.com/a/13947263/449553 . Portanto, a convenção de nomenclatura é mais rígida do que parece neste exemplo.
msangel
1
A classe de implementação em # 2 foi nomeada incorretamente: deveria ser CustomUserRepositorye não deveria CustomerUserRepository.
Cotta
28

Esta resposta pode demorar um pouco, mas eu recomendo evitar toda a rota do repositório. Você obtém poucos métodos implementados de grande valor prático. Para fazê-lo funcionar, você se depara com o absurdo da configuração do Java, no qual pode passar dias e semanas sem muita ajuda na documentação.

Em vez disso, siga a MongoTemplaterota e crie sua própria camada de acesso a dados, que o livra dos pesadelos de configuração enfrentados pelos programadores do Spring. MongoTemplateé realmente o salvador para engenheiros que se sentem confortáveis ​​arquitetando suas próprias classes e interações, pois há muita flexibilidade. A estrutura pode ser algo assim:

  1. Crie uma MongoClientFactoryclasse que será executada no nível do aplicativo e fornecerá um MongoClientobjeto. Você pode implementar isso como um Singleton ou usando um Enum Singleton (isso é thread-safe)
  2. Crie uma classe base de acesso a dados da qual você pode herdar um objeto de acesso a dados para cada objeto de domínio). A classe base pode implementar um método para criar um objeto MongoTemplate que os métodos específicos de sua classe podem usar para todos os acessos ao banco de dados
  3. Cada classe de acesso a dados para cada objeto de domínio pode implementar os métodos básicos ou você pode implementá-los na classe base
  4. Os métodos do controlador podem então chamar métodos nas classes de acesso a dados conforme necessário.
rameshpa
fonte
Olá @rameshpa. Posso usar o MongoTemplate e o repositório no mesmo projeto? .. É possível usar
Gauranga
1
Você poderia, mas o MongoTemplate que você implementa terá uma conexão diferente com o banco de dados do que a conexão usada pelo Repositório. A atomicidade pode ser um problema. Além disso, eu não recomendaria usar duas conexões diferentes em um thread se você tiver necessidades de sequenciamento
rameshpa
23

FWIW, com relação às atualizações em um ambiente multi-thread:

  • MongoTemplatefornece "atômica" out-of-the-box operações updateFirst , updateMulti, findAndModify, upsert... que lhe permitem modificar um documento em uma única operação. O Updateobjeto usado por esses métodos também permite que você direcione apenas os campos relevantes .
  • MongoRepositorysó dá-lhe as operações básicas CRUD find , insert, save, delete, que trabalham com POJOs contendo todos os campos . Isso força você a atualizar os documentos em várias etapas (1. findo documento a ser atualizado, 2. modificar os campos relevantes do POJO retornado e, em seguida, 3. saveele) ou definir suas próprias consultas de atualização manualmente usando @Query.

Em um ambiente multiencadeado, como, por exemplo, um back-end Java com vários terminais REST, as atualizações de método único são o caminho a percorrer, a fim de reduzir as chances de duas atualizações simultâneas sobrescreverem as alterações uma da outra.

Exemplo: dado um documento como este: { _id: "ID1", field1: "a string", field2: 10.0 }e duas threads diferentes atualizando-o simultaneamente ...

Com MongoTemplateele ficaria mais ou menos assim:

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

e o estado final para o documento é sempre, { _id: "ID1", field1: "another string", field2: 15.0 }pois cada thread está acessando o banco de dados apenas uma vez e apenas o campo especificado é alterado.

Considerando que o mesmo cenário de caso com MongoRepositoryseria assim:

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 o documento final sendo { _id: "ID1", field1: "another string", field2: 10.0 }ou { _id: "ID1", field1: "a string", field2: 15.0 }dependendo de qual saveoperação atinge o banco de dados por último.
(NOTA: Mesmo se usássemos a @Versionanotação do Spring Data como sugerido nos comentários, não mudaria muito: uma das saveoperações lançaria um OptimisticLockingFailureException, e o documento final ainda seria um dos anteriores, com apenas um campo atualizado em vez de ambos. )

Portanto, eu diria que essa MongoTemplateé a melhor opção , a menos que você tenha um modelo POJO muito elaborado ou precise dos recursos de consultas personalizadas MongoRepositorypor algum motivo.

walen
fonte
Bons pontos / exemplos. No entanto, seu exemplo de condição de corrida e resultado indesejado podem ser evitados usando @Version para prevenir esse mesmo cenário.
Madbreaks
@Madbreaks Você pode fornecer recursos sobre como fazer isso? Provavelmente algum médico oficial?
Karthikeyan
Documentos de dados Spring sobre anotação @Version: docs.spring.io/spring-data/mongodb/docs/current/reference/html/…
Karim Tawfik
1
@Madbreaks Obrigado por apontar isso. Sim, @Version"evitaria" o segundo encadeamento sobrescrever os dados salvos pelo primeiro - "evitaria" no sentido de que descartaria a atualização e lançaria um OptimisticLockingFailureExceptionem seu lugar. Portanto, você terá que implementar um mecanismo de repetição se quiser que a atualização seja bem-sucedida. MongoTemplate permite que você evite todo o cenário.
walen