Enables type safe
and fluent criteria API for Quarkus applications
via DeltaSpike Criteria.
-
Add the following dependency:
<dependency> <groupId>com.github.quarkus-criteria</groupId> <artifactId>quarkus-criteria</artifactId> <version>1.0.0</version> </dependency>
-
Create a JPA entity which implements
PersistenceEntity
or extendBaseEntity
@Entity @Table(name = "car") public class Car extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; @Column(name = "model") private String model; @Column(name = "name") private String name; @Column(name = "price") private Double price; @OneToOne private Brand brand; @oneToMany private List<CarSalesPoint> salesPoints; @Version private Integer version; @Override public Integer getId() { return id; }
-
To have
type safe
queries you need to enable JPA metamodel generation on your build:<plugin> <groupId>org.bsc.maven</groupId> <artifactId>maven-processor-plugin</artifactId> <version>3.3.2</version> <executions> <execution> <id>process</id> <phase>generate-sources</phase> <goals> <goal>process</goal> </goals> <configuration> <processors> <processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor</processor> </processors> </configuration> </execution> </executions> <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-jpamodelgen</artifactId> <version>5.2.12.Final</version> </dependency> </dependencies> </plugin>
💡See this tutorial for configuring it on your IDE. -
Now you’re ready to use DeltaSpike type safe fluent criteria API on your Quarkus application
For examples below consider the following entity associations:
The easiest way to use quarkus criteria is via generic injection of CrudService
:
@Inject
@Service
CrudService<Car> crudService;
Below are some query examples:
List<Car> carsFound = crudService.criteria()
.fetch(Car_.brand)
.join(Car_.brand, crudService.where(Brand.class)
.eqIgnoreCase(Brand_.name, "tesla"))
.getResultList();
assertThat(carsFound).isNotNull().hasSize(2)
.extracting("name")
.contains("Model S", "Model X");
@Inject
CrudService<Brand> brandCrud;
List<Brand> brands = brandCrud.criteria().distinct()
.join(Brand_.cars, brandCrud.where(Car.class)
.join(Car_.carSalesPoints, brandCrud.where(CarSalesPoint.class)
.join(CarSalesPoint_.salesPoint, brandCrud.where(SalesPoint.class)
.eq(SalesPoint_.address, "Tesla HQ address")))
)
.getResultList();
assertThat(brands).isNotNull().hasSize(1)
.extracting(brand -> brand.getName())
.contains("Tesla");
public List<Car> listCarsByModelBrandAndSalesPointAddress() {
return criteria()
.distinct()
.fetch(Car_.brand)
.join(Car_.brand, where(Brand.class)
.or(criteria(Brand.class).eq(Brand_.name, "Nissan"),
criteria(Brand.class).eq(Brand_.name, "Ford")))
.join(Car_.carSalesPoints, where(CarSalesPoint.class)
.join(CarSalesPoint_.salesPoint, where(SalesPoint.class)
.eqIgnoreCase(SalesPoint_.address, "ford motors address")))
.or(criteria().likeIgnoreCase(Car_.model, "%tanium"),
criteria().eq(Car_.name, "Sentra"))
.getResultList();
}
Above criteria will generate following select:
Hibernate:
select
distinct car0_.id as id1_1_0_,
brand4_.id as id1_0_1_,
car0_.brand_id as brand_id6_1_0_,
car0_.model as model2_1_0_,
car0_.name as name3_1_0_,
car0_.price as price4_1_0_,
car0_.version as version5_1_0_,
brand4_.name as name2_0_1_
from
car car0_
inner join
brand brand1_
on car0_.brand_id=brand1_.id
inner join
car_sales_point carsalespo2_
on car0_.id=carsalespo2_.car_id
inner join
sales_point salespoint3_
on carsalespo2_.SALESPOINTS_ID1=salespoint3_.ID1
and carsalespo2_.SALESPOINTS_ID2=salespoint3_.ID2
inner join
brand brand4_
on car0_.brand_id=brand4_.id
where
(
brand1_.name=?
or brand1_.name=?
)
and upper(salespoint3_.address)=?
and (
upper(car0_.model) like ?
or car0_.name=?
)
💡
|
You can also inherit from CrudService, this way you can invoke @Transactional(Transactional.TxType.SUPPORTS)
public class CarService extends CrudService<Car> implements Serializable {
public List<CarWithNameAndPrice> getCarsAndMapToDTO() {
List<CarWithNameAndPrice> carsDTO = criteria()
.select(CarWithNameAndPrice.class, attribute(Car_.name), attribute(Car_.price)) (1)
.join(Car_.brand, where(Brand.class)
.or(criteria(Brand.class)
.eq(Brand_.name, "Nissan"),
criteria(Brand.class).eq(Brand_.name, "Tesla")))
.join(Car_.salesPoints, where(SalesPoint.class)
.likeIgnoreCase(SalesPoint_.name, "%Tesla%")).getResultList();
return carsDTO;
}
|
Double result = criteria()
.select(Double.class, sum(Car_.price))
.likeIgnoreCase(Car_.model, model)
.getSingleResult();
long count = crudService.count(crudService.criteria()
.likeIgnoreCase(Car_.model, "%porche%")
.gtOrEq(Car_.price, 10000D));
List.of(new Brand().setName("Nissan"),
new Brand().setName("Ford"),
new Brand().setName("Tesla"))
.forEach(brandCrud::insert);
int deleted = crudService.removeBatch(crudService.criteria().getResultList(), 5);
assertThat(deleted).isEqualTo(10);
assertThat(crudService.count()).isEqualTo(0L);
/**
* curl -X GET http://localhost:8080/api/cars -v
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response list(@QueryParam("first") @DefaultValue("0") Integer startPosition,
@QueryParam("pageSize") @DefaultValue("10") Integer maxResult,
@QueryParam("sortField") @DefaultValue("id") String sortField,
@QueryParam("sortField") @DefaultValue("ASCENDING") SortType sortType,
@QueryParam("name") @DefaultValue("") String name,
@QueryParam("model") @DefaultValue("") String model,
@QueryParam("price") Double price,
@QueryParam("brandId") Long brandId
) {
Filter<Car> carFilter = new Filter<>(new Car().setName(name)
.setModel(model)
.setPrice(price))
.setFirst(startPosition)
.setPageSize(maxResult)
.setSortType(sortType)
.setSortField(sortField);
if(brandId != null) {
carFilter.getEntity().setBrand(new Brand(brandId));
}
return ok(carService.paginate(carFilter)).build();
}
💡
|
paginate method will use filter to get pagination information and also call configPagination on your service so you can add restrictions or fetch additional fields, see CarService for example.
|
💡
|
For more examples see: |
If you don’t want CRUD support you can inherit directly from BaseCriteriaSupport
:
@ApplicationScoped
public class CarCriteria extends BaseCriteriaSupport<Car> {
/**
* getEntityManager().createQuery("SELECT SUM(c.price) FROM Car c WHERE upper(c.model) like :model", Double.class)
* .setParameter("model", model).getSingleResult();
*/
public Double getTotalPriceByModel(String model) {
return criteria()
.select(Double.class, sum(Car_.price))
.likeIgnoreCase(Car_.model, model)
.getSingleResult();
}
}
You can query by example using exampleBuilder
:
Car carExample = new Car().model("Ferrari");
List<Car> cars = carService
.exampleBuilder.of(carExample)
.with(Car_.model) (1)
.build()
.getResultList();
-
select which attributes to consider from example entity, if no attribute is provided then non null properties from example entity will be considered.
You can also choice the comparison operation to be used when comparing example attribute value:
Car carExample = new Car().setModel("%rrari");
List<Car> cars = carService
.exampleBuilder.of(carExample)
.with(ComparisonOperation.LIKE_IGNORE_CASE, Car_.model)
.build()
.getResultList();
If no comparisonOperation is provided then EQ
will be used.
You can also provide multiple attributes:
Car carExample = new Car().setModel("porche").setName("%avenger");
List<Car> cars = crudService
.exampleBuilder
.of(carExample)
.with(ComparisonOperation.LIKE_IGNORE_CASE, Car_.name, Car_.model)
.build()
.getResultList();
Which will generate following query:
Hibernate:
select
car0_.id as id1_1_,
car0_.brand_id as brand_id6_1_,
car0_.model as model2_1_,
car0_.name as name3_1_,
car0_.price as price4_1_,
car0_.version as version5_1_
from
car car0_
where
(
upper(car0_.name) like ?
)
and (
upper(car0_.model) like ?
)
Associations will also be queried:
SalesPoint salesPoint = new SalesPoint().setAddress("Tesla HQ address");
CarSalesPoint carSalesPointExample = new CarSalesPoint().setSalesPoint(salesPoint);
List<CarSalesPoint> carSalesPointsFound = carSalesPointCrud
.exampleBuilder.of(carSalesPointExample)
.with(SalesPoint_.address)
.build()
.distinct()
.fetch(CarSalesPoint_.salesPoint) (1)
.getResultList();
assertThat(carSalesPointsFound).isNotNull().hasSize(2);
List<Car> carsFound = carSalesPointsFound.stream()
.map(carSalesPoint -> carSalesPoint.getCar())
.collect(Collectors.toUnmodifiableList());
assertThat(carsFound).isNotNull().hasSize(2)
.extracting("name")
.contains("Model S", "Model X");
-
Fetch association attribute in result list
SalesPoint salesPoint = new SalesPoint()
.setAddress("Tesla HQ address");
Set<Car> cars = Set.of(new Car().addSalesPoint(salesPoint));
Brand brandExample = new Brand().setCars(cars);
List<Brand> brands = brandCrud.exampleBuilder
.of(brandExample)
.with(EQ, SalesPoint_.address)
.build()
.distinct()
.getResultList();
assertThat(brands).isNotNull().hasSize(1)
.extracting(brand -> brand.getName())
.contains("Tesla");
The example criteria above will generate following select:
Hibernate:
select
distinct brand0_.id as id1_0_,
brand0_.name as name2_0_
from
brand brand0_
inner join
car cars1_
on brand0_.id=cars1_.brand_id
inner join
car_sales_point carsalespo2_
on cars1_.id=carsalespo2_.car_id
inner join
sales_point salespoint3_
on carsalespo2_.SALESPOINTS_ID1=salespoint3_.ID1
and carsalespo2_.SALESPOINTS_ID2=salespoint3_.ID2
where
salespoint3_.address=?
Brand brand = new Brand().setName("%ssan");
Car carExample = new Car().setModel("SE").setName("%tra")
.setPrice(12.999)
.setBrand(brand);
Car car = (Car) carService
.exampleBuilder.of(carExample)
.with(Car_.model) //EQ
.with(LIKE_IGNORE_CASE, Brand_.name, Car_.name)
.with(LT_OR_EQ, Car_.price)
.build()
.fetch(Car_.brand)
.getSingleResult();
assertThat(car).isNotNull()
.extracting(Car::getName, Car::getModel, Car::getBrand)
.contains("Sentra", "SE", new Brand(2L));//brand id=2 is nissan
The following select is generated from above example:
Hibernate:
select
car0_.id as id1_1_0_,
brand2_.id as id1_0_1_,
car0_.brand_id as brand_id6_1_0_,
car0_.model as model2_1_0_,
car0_.name as name3_1_0_,
car0_.price as price4_1_0_,
car0_.version as version5_1_0_,
brand2_.name as name2_0_1_
from
car car0_
inner join
brand brand1_
on car0_.brand_id=brand1_.id
inner join
brand brand2_
on car0_.brand_id=brand2_.id
where
car0_.model=?
and (
upper(brand1_.name) like ?
)
and (
upper(car0_.name) like ?
)
and car0_.price<=12.999
Car carExample = new Car()
.setName("Fusion")
.setModel("S")
.setPrice(10.000D)
.setBrand(new Brand().setName("Nissan"));
List<Car> cars = crudService
.exampleBuilder.of(carExample)
.with(GT_OR_EQ, Car_.price)
.or(Car_.name, Car_.model, Brand_.name)
.build()
.getResultList();
assertThat(cars).isNotNull()
.extracting(Car::getName)
.contains("Fusion", "Sentra", "Model S")
.doesNotContain("Model X");
Following select will be generated:
Hibernate:
select
car0_.id as id1_1_,
car0_.brand_id as brand_id6_1_,
car0_.model as model2_1_,
car0_.name as name3_1_,
car0_.price as price4_1_,
car0_.version as version5_1_
from
car car0_
inner join
brand brand1_
on car0_.brand_id=brand1_.id
where
car0_.price>=10.0
or car0_.name=?
or car0_.model=?
or brand1_.name=?
💡
|
More examples can be found here. |
System tests are located in system-tests
module, to run the tests use following maven command:
mvn test
To run system tests in native mode:
mvn verify -Pnative
To run native tests inside docker (without the need to install GraalVM):
-
MacOS:
mvn verify -Dquarkus.native.container-build=true -Dquarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-mandrel:20.3-java11 -Pnative
-
Linux
mvn verify -Dquarkus.native.container-build=true -Dquarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-native-image:20.3.0-java11 -Pnative
The system tests example application needs a PostgreSQL database running, use the following docker command to spin up one:
docker run --ulimit memlock=-1:-1 -it --rm=true --memory-swappiness=0 --name quarkus_test -e POSTGRES_USER=car -e POSTGRES_PASSWORD=car -e POSTGRES_DB=cars -p 5432:5432 postgres:11.5
After that just build and start the application:
cd system-tests && mvn package -DskipTests && java -jar target/quarkus-criteria-st-runner.jar