fracz
fracz

Reputation: 21258

How does Spring build its like* queries in mongo?

I have the following documents in my Mongo database (_ids skipped):

> db.names.find({})
{ "name": "John" }
{ "name": "Jack" }
{ "name": "Johny" }
{ "name": "Jenny" }

I have created the Spring Data repository for this collection:

public interface NameRepository extends MongoRepository<Name, ObjectId> {
    Collection<Name> findByNameLike(String name);
}

The results are as follows:

nameRepository.findByNameLike("John"); // John, Johny
nameRepository.findByNameLike("John$"); // John
nameRepository.findByNameLike("J*ny"); // Johny, Jenny
nameRepository.findByNameLike("Jo?*ny"); // Johny, Jenny
nameRepository.findByNameLike("Jo[]]?*ny"); // empty, without error

So it behaves like a smart regular expression. Now I want to implement such behavior myself:

class MyNameRepository {
    MongoOperations mongoOperations; // injected

    public Collection<Name> findByName(String like) {
        Query query = new Query();
        query.addCriteria(Criteria.where("name").regex(like));
        return mongoOperations.find(query, Name.class)
    }
}

But the behavior of my method is different:

myNameRepository.findByName("John"); // John, Johny
myNameRepository.findByName("John$"); // John
myNameRepository.findByName("J*ny"); // empty, without error
myNameRepository.findByName("Jo?*ny"); // error (invalid regex)
myNameRepository.findByName("Jo[]]?*ny"); // error (invalid regex)

So my custom methods works exactly as I would have defined findByNameRegex method in Spring data repository. How to implement the findByNameLike behavior?

Why I need this: When I need filtering my collection by many attributes (e.g. by name only, by name and surname, by name and postal code, by name, surname and postal code etc.) its easier for me to support queries as parameters rather than create many very-long-named methods in repository.

Upvotes: 5

Views: 9823

Answers (1)

fracz
fracz

Reputation: 21258

Actually, there is a simple method toLikeRegex that converts all * to .*. Together with catching invalid regular expressions, my custom implementation may look like this:

class MyNameRepository {
    MongoOperations mongoOperations; // injected

    public Collection<Name> findByName(String like) {
        try{
            Query query = new Query();
            query.addCriteria(Criteria.where("name").regex(toLikeRegex(like)));
            return mongoOperations.find(query, Name.class);
        } catch(PatternSyntaxException e) {
            return Collections.emptyList();
        }
    }

    private String toLikeRegex(String source) {
        return source.replaceAll("\\*", ".*");
    }
}

and behaves the same as Spring Data repository does.


Update 19 Apr 2016

There is a MongoRegexCreator class since 1.9.0.RELEASE that provides a toRegularExpression method.

String regex = MongoRegexCreator.INSTANCE
               .toRegularExpression(userString, Part.Type.EXISTS);

Upvotes: 3

Related Questions