Any object that implements org.apache.cayenne.query.Query interface can be executed with a DataContext.
Understanding Query Interface
The interface defines the following methods (ommitting irrelevant deprecated ones):
public interface Query extends Serializable { String getName(); QueryMetadata getMetaData(EntityResolver resolver); void route(QueryRouter router, EntityResolver resolver, Query substitutedQuery); SQLAction createSQLAction(SQLActionVisitor visitor); }
- getName returns a symbolic name of the query. The name may be used as a key to find queries stored in the DataMap. Some query implementors reuse the name as a QueryMetadata cache key. Generally the name can be null.
- getMetaData is called at various stages of the execution by Cayenne access stack to retrieve query parameters. EntityResolver instance is passed to this method, meaning that the query doesn't need to store direct references to Cayenne mapping objects and can resolve them at runtime.
- route is invoked when Cayenne decides which DataNode (database) to use when running a query. Routing decision is made by the Query istelf by calling QueryRouter.route(..). This is an important extension point. For instance all Cayenne "indirect" queries implement this method to create (or somehow resolve) one or more substitute queries and route them instead of self.
- createSQLAction allows to fully customize Query execution at the JDBC level. In the simplest case an implementor calls one of the methods on SQLActionVisitor to return a standard action for a specific type of query. However it can provide its own SQLAction that accesses the database in some special way.
Indirect Queries
One customization strategy is an "indirect" query that encapsulates some user-defined operation and in runtime resolves to one or more standard Cayenne queries. Indirect queries can be created from scratch or by extending org.apache.cayenne.query.IndirectQuery. As an example lets implement a "CountQuery" query that returns a number of rows in a given table:
public class CountQuery extends IndirectQuery { protected Class objectClass; public CountQuery(Class objectClass) { this.objectClass = objectClass; } protected Query createReplacementQuery(EntityResolver resolver) { DbEntity entity = resolver.lookupDbEntity(objectClass); if (entity == null) { throw new CayenneRuntimeException( "No entity is mapped for java class: " + objectClass.getName()); } String sql = "SELECT #result('count(*)' 'int' 'C') FROM " + entity.getName(); SQLTemplate replacement = new SQLTemplate(entity, sql); replacement.setFetchingDataRows(true); return replacement; } }
Now you can run the query like that:
CountQuery query = new CountQuery(Artist.class); DataContext context = DataContext.createDataContext(); Map row = (Map) context.performQuery(query).get(0); System.out.println("Count: " + row.get("C"));
For other real-life examples of indirect queries take a look at the source code of the following Cayenne queries: QueryChain, ObjectIdQuery, RelationshipQuery, NamedQuery.
Subclassing Standard Queries
All standard queries can be subclassed, overriding some of their methods. For instance overriding route and/or createSQLAction would allow to implement custom callbacks at different points of query lifecycle.