Documentation / Query / Background / Query Beans
Overview
Ebean provides a mechanism for building type safe queries by generating query beans
.
The design/approach taken for the query beans
and type safe query construction is orientated
for maximum readability and to achieve that with Java (which does not have properties support) has meant
that we need to use AOP to enhance the query beans
. The AOP enhancement can be done via:
IntelliJ IDEA Plugin
- Ideal for use during developmentjavaagent
- 'Ok' during development, Good for production deploymentmaven plugin
- Good for production deployment
Recommendation
The recommendation is that during development the IntelliJ IDEA Plugin
is used as that is
just very easy to use relative to specifying a javaagent
when running tests etc.
For deployment/production you can use the javaagent
or maven plugin
.
Benefits
Model changes
When entity bean model changes the query beans are regenerated. If there are queries that are broken by a model change then this results in a compile error (rather than a runtime error) and so breaking model changes are detected easily and early.
Typed bind values
Using query beans means that the bind values used for any given property must be compatible with the properties type. For example, you can bind a String value for a property that has type Timestamp.
Speed of development
Using query beans means we can use IDE auto completion to help write our queries. There is no need to check the model when we build our queries. This makes development a little bit faster, safer and fun!!
Long term maintenance
When applications get bigger and older using strongly type query beans makes maintenance easier as it helps guide new developers who are less familiar with the model and model changes across large applications are much less risky due to compile type checking relative to string based queries.
Examples
Example
List<Customer> customers =
new QCustomer()
.id.greaterThan(12)
.name.startsWith("Rob")
.findList();
Example: nested or() and and()
The following example contains an or()
containing a nested and()
.
List<Customer> customers
= new QCustomer()
.billingAddress.city.equalTo("Auckland")
.version.between(1,100)
.billingAddress.country.equalTo(nz)
.registered.before(new Date())
.or()
.id.greaterThan(1)
.and()
.name.icontains("Jim")
.inactive.isTrue()
.endAnd()
.endOr()
.orderBy()
.name.asc()
.id.desc()
.findList();
Example: filterMany
filterMany provides the ability to filter the returned objects of an associated OneToMany or ManyToMany relationship.
For example, lets say Customers have a lot of orders and for a specific use case we want to find customers and only want their recent new orders. We want to fetch the orders (a OneToMany) for the customers but want to filter those orders to only contain 'recent new orders'
Date oneWeekAgo = ...
// Use QOrder to create an expression list ...
// .. use this expression list to then filter orders
ExpressionList<Order> recentNewOrders = new QOrder()
.orderDate.after(oneWeekAgo)
.status.eq(Order.Status.NEW)
.getExpressionList();
List<Customer> customers = new QCustomer()
.name.ilike("Rob")
// filter the orders (not customers)
.orders.filterMany(recentNewOrders)
.findList();
Example: select() and fetch()
Use the alias()
method to get a shared instance of the query bean
that can be used to specify properties for select()
and fetch()
.
Note that the 'alias' instance of the query bean returned by the alias()
method
does not have an underlying query associated with it and can only be used to provide properties
to select()
and fetch()
.
// special 'alias' instances of query beans
// used to supply properties to the select() and fetch()
QCustomer cus = QCustomer.alias();
QContact con = QContact.alias();
List<Customer> customers = new QCustomer()
// query tuning
.select(cus.name, cus.inactive)
.contacts.fetch(con.email, con.firstName)
// predicates
.name.ilike("Rob")
.findList();
// special 'alias' instances of query beans
// used to supply properties to the select() and fetch()
QContact con = QContact.alias();
QCustomer cus = QCustomer.alias();
QProduct pro = QProduct.alias();
Customer customer =
Customer.find.where()
// manual query tuning
.select(cus.name, cus.registered)
.orders.fetchAll()
.orders.details.product.fetch(pro.name)
.contacts.fetch(con.firstName)
// predicates
.id.eq(12)
.findOne();
APT / KAPT (generating query beans)
To generate Java
query beans we use the io.ebean:querybean-generator
java annotation processor and to generate Kotlin
query beans we use
io.ebean:kotlin-querybean-generator.
Generating with Maven
Refer to docs / getting-started / maven for details of how to generate query beans using maven.
Generating with Gradle
Refer to docs / getting-started / gradle for details of how to generate query beans using gradle.
Enhancement
Note that prior to version 12.1.8 we need to edit a src/main/resources/ebean.mf
manifest file to specify querybean-packages
for the packages that should
be enhanced for query beans. This is no longer needed from 12.1.8 onwards.
Manual generation
There is a mechanism to generate query beans via running code (not via javac annotation processing). This is manual in the sense that whenever there is a change to the entity beans we then need to re-run this process (where as with the java annotation processor it is automatically updated with each compile).
<dependency>
<groupId>io.ebean.tools</groupId>
<artifactId>finder-generator</artifactId>
<version>12.1.1</version>
</dependency>
To generate the query beans
add code similar to below to your src/test
.
The Generator reads compiled .class
entity beans using the ASM
library.
It then uses this meta data to generate java source for the query beans
.
Using this approach rather than using javac
annotation processing means there is
no requirement for javac - you can use any JVM language to write/compile your
entity beans (so maybe Scala
and SBT.
The requirement of this approach is that entity beans are compiled as .class files and that
GeneratorConfig
is configured to know where the classes are, where the source
code should go and the package containing the entity beans.
package main;
import org.avaje.ebean.typequery.generator.Generator;
import org.avaje.ebean.typequery.generator.GeneratorConfig;
import java.io.IOException;
/**
* Generate type query beans for each entity bean.
*/
public class MainQueryBeanGenerator {
public static void main(String[] args) throws IOException {
GeneratorConfig config = new GeneratorConfig();
//config.setClassesDirectory("./target/classes");
//config.setDestDirectory("./src/main/java");
//config.setDestResourceDirectory("./src/main/resources");
config.setEntityBeanPackage("org.example.domain");
//config.setDestPackage("org.example.domain.query");
//config.setOverwriteExistingFinders(true);
Generator generator = new Generator(config);
generator.generateQueryBeans();
// Additionally generate 'finder's
//generator.generateFinders();
//generator.modifyEntityBeansAddFinderField();
}
}