spring-projects / spring-data-mongodb

Provides support to increase developer productivity in Java when using MongoDB. Uses familiar Spring concepts such as a template classes for core API usage and lightweight repository style data access.
https://spring.io/projects/spring-data-mongodb/
Apache License 2.0
1.62k stars 1.09k forks source link

Expose new QueryDSL elemMatch feature [DATAMONGO-595] #1524

Closed spring-projects-issues closed 6 years ago

spring-projects-issues commented 11 years ago

Raman Gupta opened DATAMONGO-595 and commented

QueryDSL, as of version 2.5.0, supports building queries with the MongoDB $elemMatch operator:

https://github.com/mysema/querydsl/issues/115

This functionality is available from MongodbQuery using the builder returned by the new method anyEmbedded. However, I don't see any way to access this functionality via QueryDslMongoRepository.

See also: https://github.com/mysema/querydsl/issues/324


Affects: 1.1.1

Issue Links:

3 votes, 6 watchers

spring-projects-issues commented 10 years ago

Phillip Wirth commented

I would like to see this feature too. I don't know any way to create a query like the following using query-dsl while using spring-data-mongodb.

db.foo.find({ 
    "bar" : 5051 
},  
{bar : 1,
    'innerDoc' : { 
        $elemMatch: {
        'innerFoo' : "nice"
        }
    }
})
spring-projects-issues commented 8 years ago

vinoth kumar commented

Hi Oliver, Please let me know does this issue resolved in latest version of querydsl? I am in need of this feature for querydsl spring mongo repository

If so Can you please let me know where can I find the documentation

spring-projects-issues commented 7 years ago

ZhangLiangliang commented

  1. In querydsl issue-324 , noticed the comment by "timowest"

anyEmbedded() is on the query level, since it involves a join alias which are always declared on the query level. Another reason is that the Expression types are the same for most Querydsl modules. It is also documented like this in #115.

So does this mean there is no method like "elemMatch" mothod on generated "QXxx" classes ?

  1. but followed the tip by timowest, tested and found com.querydsl.core.types.dsl.CollectionPathBase#any() worked for embedded documents (but not @DBRef field) when using queryDSL:
userRepo.findAll(Expressions.allOf(
    QUser.user.tags.any().in("tag1", 'tag99')
))
  1. since anyEmbedded method is defined on com.querydsl.mongodb.AbstractMongodbQuery, we can use it with SpringDataMongodbQuery. But QueryDslMongoRepository#createQuery() is private method. So maybe it should change to protected or public, then add some convenient method?

    Before this was changed , have to do it myself with custom repository implement:

@NoRepositoryBean
public interface MyRepository<T, ID extends Serializable>
        extends MongoRepository<T, ID>, QueryDslPredicateExecutor<T> {
    public abstract AbstractMongodbQuery<T, SpringDataMongodbQuery<T>> query();
}
public class MyRepositoryImpl<T, ID extends Serializable>
        extends QueryDslMongoRepository<T, ID>
        implements MongoRepository<T, ID>,  MyRepository<T, ID> {

    public MyRepositoryImpl(MongoEntityInformation<T, ID> entityInformation, MongoOperations mongoOperations) {
        super(entityInformation, mongoOperations);

        EntityPath<T> path = SimpleEntityPathResolver.INSTANCE.createPath(entityInformation.getJavaType());

        this.builder = new PathBuilder<T>(path.getType(), path.getMetadata());
        this.entityInformation = entityInformation;
        this.mongoOperations = mongoOperations;
    }

    public MyRepositoryImpl(MongoEntityInformation<T, ID> entityInformation, MongoOperations mongoOperations, EntityPathResolver resolver) {
        super(entityInformation, mongoOperations, resolver);

        EntityPath<T> path = resolver.createPath(entityInformation.getJavaType());

        this.builder = new PathBuilder<T>(path.getType(), path.getMetadata());
        this.entityInformation = entityInformation;
        this.mongoOperations = mongoOperations;
    }

    public AbstractMongodbQuery<T, SpringDataMongodbQuery<T>> getQuery() {
        return new SpringDataMongodbQuery<T>(mongoOperations, entityInformation.getJavaType());
    }

    private final PathBuilder<T> builder;
    private final EntityInformation<T, ID> entityInformation;
    private final MongoOperations mongoOperations;
}
@Configuration
@EnableMongoRepositories(repositoryBaseClass = MyRepositoryImpl.class, basePackageClasses = MyRepository.cass)
@EnableMongoAuditing
class MongoConf {
}
AbstractMongodbQuery<Addr, SpringDataMongodbQuery<Addr>> query = addrRepo.getQuery()
query.anyEmbedded(QAddr.addr.streetList, null).on(
        QAddr_Street.street.name.in("street-2", "street-200")
)
List<Addr> addrList = query.fetchResults().getResults()
return addrList
spring-projects-issues commented 6 years ago

Hong Quang commented

Hi @ZhangLiangliang: Could you please post your full source code? How can instantiate the Object MongoEntityInformation<T, ID> entityInformation, MongoOperations mongoOperations

spring-projects-issues commented 6 years ago

ZhangLiangliang commented

@Hong Quang That's almost the full source code. I'm using spring-boot + spring-data-mongo. MongoEntityInformation, MongoOperations is instantiated by the framework.

I think you need to read Adding custom behavior to all repositories.

Still intresting how they were instantiated ? Just using github's advanced searching , such as Where MongoEntityInformation is used?, then trace, debug.

spring-projects-issues commented 6 years ago

Christoph Strobl commented

Resolved via DATAMONGO-1848SpringDataMongodbQuery now exposes anyEmbedded.

new SpringDataMongodbQuery<>(mongoTemplate, Person.class)
    .where()
    .anyEmbedded(QPerson.person.shippingAddresses, QAddress.address)
    .on(QAddress.address.planet.eq("Tatooine"))
    .fetch();
Eitraz commented 9 months ago

As I spent some time searching the web for a solution to fit my needs I just want to share how I (miss)-use the elemMatch functionality.

I came up with this util method:

public static <D, Q extends SimpleExpression<D>> PredicateOperation elemMatch(@NonNull ListPath<D, Q> path,
                                                                              @NonNull Q queryDocument,
                                                                              @NonNull List<Function<Q, Predicate>> predicates) {
    return ExpressionUtils.predicate(
            MongodbOps.ELEM_MATCH,
            path.any(),
            ExpressionUtils.allOf(predicates
                    .stream()
                    .map(predicate -> predicate.apply(queryDocument))
                    .toList())
    );
}

That I use in the following way when building predicates:

personRepository.findAll(
        QPerson.person.name.eq("Luke")
                .and(elemMatch(
                        QPerson.person.shippingAddresses,
                        QAddress.address,
                        List.of(
                                address -> address.planet.eq("Tatooine"),
                                address -> address.planet.region.eq("Outer Rim")
                        )
                ))
);

I hope it can help someone with the same problem :)

Regards, Petter