Building a Fluent Query

Queries in Qi4j can either be created from the Fluent Query API, or looked up by name from a list of native queries. In both cases, the application code is intended to become indexing subsystem-independent, but Named Native Queries requires that the bootstrap code is tailored to the indexing subsystem put in place.

Introduction to Fluent Query API


The Fluent Query API is based around the concept that you write the query expression using Java, and the intention of the query expressed will be translated into the underlying query language that is currently in use. Let's look at a simple example;
import static org.qi4j.api.query.QueryExpressions.eq;
import static org.qi4j.api.query.QueryExpressions.templateFor;

public class PersonRepositoryMixin
    implements PersonRepository
{
    @Structure UnitOfWorkFactory uowf;
    @Structure QueryBuilderFactory qbf;

    public Query<Person> findPersonsByOccupation( String occupation )
    {
        UnitOfWork uow = uowf.currentUnitOfWork();
        QueryBuilder<Person> qb = qbf.newQueryBuilder( Person.class );
        Person template = templateFor( Person.class );
        return qb
            .where(
                eq( template.occupation(), occupation ) )
            .newQuery( uow );
    }
}

In the above example, we are asking a repository to provide us with a query that can be used to retrieve the Person instances that has the occupation property set to the given string. The template is a representation of the queried entities, so that we can indicate what those values should be. In this case, we are using the same type as the returned type, but if for instance we have properties in private mixins that we want to query about, we can use those private mixin types as templates.

So how do we use this?

Executing a Query

// First of all we obtain the query
PersonRepository repo = ...;
Query<Person> query = repo.findPersonsByOccupation( "ballet dancer" );

// When the iterator() method is called in the Query,
// the query is executed.
for( Person person : query )
{
    // take care of the result.
}

The query in the above example is not executed until the iterator() method is called on the Query instance, which happens behind the scenes in the for() loop.

Pagination

If we want to paginate our query results, we call the firstResult() and maxResults() methods on the Query, prior to making the iterator() call.
// We can do paging operations
query.maxResults().set( 100 );

query.firstResult().set( 0 );
Iterator firstPage = query.iterator();

// do something...

query.firstResult().set( 100 );
Iterator secondPage = query.iterator();

// do something...

query.firstResult().set( 200 );
Iterator thirdPage = query.iterator();

Here we retrieve the result in chunks of 100 items per page. The firstResult() method is zero-indexed, and has the default value of zero. The maxResults() method has 'no limit' as the default.

Ordering

Queries often need to be ordered/sorted according to one or more fields. Qi4j supports this in the Fluent Query API via the orderBy() method on the Query interface. The method takes one or more OrderBy templates as input, and the ordering will be on those fields, with the highest priority first. Let's look at an example, as it will make things clearer. Let's say that we want to order the above query by surname and firstname.
import static org.qi4j.api.query.QueryExpressions.orderBy;

QueryBuilder<Person> qb = qbf.newQueryBuilder( Person.class );
Person template = templateFor( Person.class );
Query query = qb.where(
                eq( template.occupation(), occupation ) )
            .newQuery( uow );

query.orderBy( orderBy( template.lastName()),
               orderBy( template.firstName())
);

The QueryExpressions.orderBy() method also has a Order argument, if needed. Default Order is ascending, but if the opposite is needed, the do the following;
query.orderBy( orderBy( template.lastName(), OrderBy.Order.DESCENDING) );

Cross type querying

In the above example, we had the same type for both the return value as well as the interface used for the query parameters. Since Qi4j has a very strong encapsulation of mixins, it is often more common to query on the internal state of the composite, and return the composite instance being matched. It should be very obvious from the above code what goes where.
Let's say we have a role called Trusted implemented by many types of things, such as people, vehicles and software. Now imagine we want to search for the Trusted role, which has a firstName of "John".
QueryBuilder<Trusted> qb = qbf.newQueryBuilder( Trusted.class );
Person template = templateFor( Person.class );
Query query = qb.where(
                eq( template.firstName(), "John" ) )
            .newQuery( uow );
The interesting part of the above is that not all Trusted entities are also of type Person. Thanks to Qi4j's separation of Indexing & Query from Storage & Retrieval, it is possible to make these kind of mixed type queries, and not forcing the same structure for all entities of the same type. This is very similar to how search engines work for the world-wide web, all sites are different, yet they can be indexed and queried.

On top of that, the templateFor() method can be used to query private mixins that are not visible to the outside of the composite.


Qi4j and the Qi4j logo are trademarks of Richard Öberg, Niclas Hedhman and the members of the Qi4j Core Team. See Qi4j licensing for more information.
Powered by SiteVisionexternal link.