135

I need to write an application with which I can do complex queries using spring-data and MongoDB. I started using the MongoRepository but struggled with complex queries to find examples or understand the Syntax.

I'm talking about queries like this:

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

or the use of JSON based queries which I tried by trial and error because I didn't get the syntax right. Even after reading the MongoDB documentation (non-working example due to the wrong 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);
} 

After reading all the documentation it seems that mongoTemplate is far better documented than MongoRepository. I'm referring to the following documentation:

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

Can you tell me which is more convenient and powerful to use? mongoTemplate or MongoRepository? Are both same mature or does one lack more features than the other?

Shourya Sharma
  • 547
  • 4
  • 23
Christopher Armstrong
  • 3,477
  • 6
  • 34
  • 40

3 Answers3

173

"Convenient" and "powerful to use" are contradicting goals to some degree. Repositories are by far more convenient than templates but the latter of course give you more fine-grained control over what to execute.

As the repository programming model is available for multiple Spring Data modules, you'll find more in-depth documentation for it in the general section of the Spring Data MongoDB reference docs.

TL;DR

We generally recommend the following approach:

  1. Start with the repository abstract and just declare simple queries using the query derivation mechanism or manually defined queries.
  2. For more complex queries, add manually implemented methods to the repository (as documented here). For the implementation use MongoTemplate.

Details

For your example this would look something like this:

  1. Define an interface for your custom code:

    interface CustomUserRepository {
    
      List<User> yourCustomMethod();
    }
    
  2. Add an implementation for this class and follow the naming convention to make sure we can find the class.

    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. Now let your base repository interface extend the custom one and the infrastructure will automatically use your custom implementation:

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

This way you essentially get the choice: everything that just easy to declare goes into UserRepository, everything that's better implemented manually goes into CustomUserRepository. The customization options are documented here.

temporary_user_name
  • 35,956
  • 47
  • 141
  • 220
Oliver Drotbohm
  • 80,157
  • 18
  • 225
  • 211
  • 2
    Hi Oliver, this actually doesn't work. spring-data tries to auto-generate a query out of the custom name. yourCustomMethod(). It will say "your" is not a valid field in the domain class. I followed the manual and also double checked how you are doing it the spring-data-jpa-examples. No luck. spring-data always tries to auto-generate as soon I extend the custom interface to the repository class. The only difference is that I'm using MongoRepository and not CrudRepository as I don't want to work with Iterators for now. If you'd have a hint it would be appreciated. – Christopher Armstrong Jun 12 '13 at 01:46
  • 13
    The most common mistake is to name the implementation class wrong: if your base repo interface is called `YourRepository`, the implementation class has to be named `YourRepositoryImpl`. Is that the case? If so I'm happy to take a look at a sample project on GitHub or the like… – Oliver Drotbohm Jun 12 '13 at 09:24
  • 5
    Hi Oliver, the Impl class was named wrong as you have assumed. I adjusted the name and it looks like its working now. Thanks very much for your feedback. Its really cool to be able to use different kind of query options this way. Well thought through! – Christopher Armstrong Jun 13 '13 at 10:29
  • This answer is not so clear. After doing everything by this example i fall to this issue: http://stackoverflow.com/a/13947263/449553. So naming convention is more strict than it looks like from this example. – msangel Sep 25 '13 at 23:33
  • and how to combine those with auto-implemented queries by properties http://www.deviantpics.com/images/2015/01/06/y.png – fpopic Jan 06 '15 at 12:36
  • Note that the implementation class should be based on "declared interface class". So Spring Data based declared class is UserRepository, custom method declared is CustomUserRepository. The implementation class name is UserRepositoryImpl. Spring data searches for an existing implementation class for declared repository. – rohitmohta Mar 27 '15 at 12:25
  • @OliverGierke Are custom implementation supported with Standalone MongoRepositories? In other words, I'm not using spring DI, but just using the mongo repository factory to create a repository. Repositories work in my DI container, but not the custom ones. They are just failing that it can't find a property name like message above. return mongoRepositoryFactory.getRepository(InvoiceSequenceRepository.class, InvoiceSequenceRepositoryImpl.class); – Roy Kachouh Jun 30 '15 at 15:05
  • @RoyKachouh - Please open a new question. – Oliver Drotbohm Jul 03 '15 at 16:30
  • 1
    The implementation class on #2 is named wrong: should be `CustomUserRepository` and not `CustomerUserRepository`. – Cotta Apr 17 '16 at 22:32
  • @Cotta - Thanks, that's fixes! – Oliver Drotbohm Apr 18 '16 at 10:46
  • @OliverDrotbohm Late to the party, but is there a reason you are using `MongoOperations` instead of `MongoTemplate` in your example? – Bennett Dams Mar 26 '20 at 11:58
  • 1
    Using Spring Data Repository and MongoTemplate will create 2 connections to Mongo database server. Doesn't it true? What are the performance impacts of this? – iamcrypticcoder May 16 '20 at 15:50
  • @OliverDrotbohm in your answer, it looks like you were going to put a hyperlink for #2 "as documented here", but there's no link there. Which part of the Spring docs were you referring to for adding manually implemented methods on to an existing repository? – takanuva15 Aug 02 '23 at 22:19
47

FWIW, regarding updates in a multi-threaded environment:

  • MongoTemplate provides "atomic" out-of-the-box operations updateFirst, updateMulti, findAndModify, upsert... which allow you to modify a document in a single operation. The Update object used by these methods also allows you to target only the relevant fields.
  • MongoRepository only gives you the basic CRUD operations find, insert, save, delete, which work with POJOs containing all the fields. This forces you to either update the documents in several steps (1. find the document to update, 2. modify the relevant fields from the returned POJO, and then 3. save it), or define your own update queries by hand using @Query.

In a multi-threaded environment, like e.g. a Java back-end with several REST endpoints, single-method updates are the way to go, in order to reduce the chances of two concurrent updates overwriting one another's changes.

Example: given a document like this: { _id: "ID1", field1: "a string", field2: 10.0 } and two different threads concurrently updating it...

With MongoTemplate it would look somewhat like this:

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

and the final state for the document is always { _id: "ID1", field1: "another string", field2: 15.0 } since each thread is accesing the DB only once and only the specified field is changed.

Whereas the same case scenario with MongoRepository would look like this:

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

and the final document being either { _id: "ID1", field1: "another string", field2: 10.0 } or { _id: "ID1", field1: "a string", field2: 15.0 } depending on which save operation hits the DB last.
(NOTE: Even if we used Spring Data's @Version annotation as suggested in the comments, not much would change: one of the save operations would throw an OptimisticLockingFailureException, and the final document would still be one of the above, with only one field updated instead of both.)

So I'd say that MongoTemplate is a better option, unless you have a very elaborated POJO model or need the custom queries capabilities of MongoRepository for some reason.

walen
  • 7,103
  • 2
  • 37
  • 58
  • Good points/examples. However your race condition example and undesired result can be avoided using @Version to prevent that very scenario. – Madbreaks Jul 06 '18 at 19:44
  • @Madbreaks Can you provide any resources on how to achieve this ? Any official doc probably ? – Karthikeyan Jun 19 '19 at 05:11
  • Spring data docs about @Version annotation: https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mongo-template.optimistic-locking – Karim Tawfik Jan 23 '20 at 09:58
  • 2
    @Madbreaks Thanks for pointing that out. Yes, `@Version` would "avoid" the second thread overwriting the data saved by the first — "avoid" in the sense that it would discard the update and throw an `OptimisticLockingFailureException` instead. So you'd have to implement a retry mechanism if you want the update to succeed. MongoTemplate allows you to avoid the whole scenario. – walen Jan 23 '20 at 11:30
  • 1
    The ability to update parts of an entity from different threads, without considering the entity as a whole, as exciting as it seems, from technology point of view, is a bad practice, that could lead to more questions than answers. Some systems with high concurrency requirements are designed in such a way that no updates are made at all, but only inserts. – Vlad Barjovanu Feb 11 '23 at 15:55
33

This answer may be a bit delayed, but I would recommend avoiding the whole repository route. You get very little implemented methods of any great practical value. In order to make it work you run into the Java configuration nonsense which you can spend days and weeks on without much help in the documentation.

Instead, go with the MongoTemplate route and create your own Data access layer which frees you from the configuration nightmares faced by Spring programmers. MongoTemplate is really the savior for engineers who are comfortable architecting their own classes and interactions since there is lot of flexibility. The structure can be something like this:

  1. Create a MongoClientFactory class that will run at the application level and give you a MongoClient object. You can implement this as a Singleton or using an Enum Singleton (this is thread safe)
  2. Create a Data access base class from which you can inherit a data access object for each domain object). The base class can implement a method for creating a MongoTemplate object which you class specific methods can use for all DB accesses
  3. Each data access class for each domain object can implement the basic methods or you can implement them in the base class
  4. The Controller methods can then call methods in the Data access classes as needed.
rayryeng
  • 102,964
  • 22
  • 184
  • 193
rameshpa
  • 555
  • 5
  • 5
  • Hi @rameshpa Can I use both MongoTemplate & repository in same project ?..Is it possible to use – Gauranga Jul 16 '17 at 14:49
  • 3
    You could but the MongoTemplate that you implement will have a different connection to the DB than the connection used by Repository. Atomicity could be an issue. Also I would not recommend using two different connections on one thread if you have sequencing needs – rameshpa Aug 07 '17 at 04:27