Pass a object as query parameter and exclude the ID column - java

I have the following:
#Entity
#NamedQuery(name = "listCarsBySecurity", query = "SELECT c FROM Car c WHERE c.security = :security"
public class Car {
#Id
#GeneratedValue
private Long id;
#NotNull()
#Column(nullable = false)
private String make;
#NotNull()
#Column(nullable = false)
private String model;
// Some more fields
#NotNull()
#OneToOne (fetch = FetchType.LAZY, orphanRemoval=true)
private Security security = new Security();
// Some getters and setters
As you can see, the Car class has a "Security" object which is LAZY fetched. The security class looks like:
#Entity
public class Security {
#Id #GeneratedValue
private Long id;
// Security equipment. Add in alphanumerical order
private boolean abs;
private boolean airbag;
private boolean antispin;
// Some getters and setters
as you can see, the named query list try to list all cars which has a security entity equal to a provided security object.
The persistence method looks like:
#Stateless
public class CarEJB {
#PersistenceContext(unitName = "carcmsPU")
private EntityManager em;
public List<Car> listCarsBySecurity(Security security) {
TypedQuery<Car> query = em.createNamedQuery("listCarsBySecurity", Car.class);
query.setParameter("security", security);
return query.getResultList();
}
And a junit test looks like:
#Test
public void searchCar() throws Exception {
// Looks up the EJBs
carEJB = (CarEJB) ctx.lookup("java:global/classes/CarEJB");
// Create a new Ferrari with security = ABS brakes and Airbag
Car car = new Car();
car.setMake("Ferrari");
car.setModel("Some model");
car.setSubModel("Some sub model");
car.setEngine("Some engine");
car.setYear(1999);
car.getFuel().setGasoline(true);
car.setGearbox(Gearbox.AUTOMATIC);
car.setKilometres(323);
car.setDescription("This is a description");
Security security = new Security();
security.setAbs(true);
security.setAirbag(true);
car.setSecurity(security);
carEJB.createCar(car); // Persist
// Create a new security object and match it to the former one
Security securityObject = new Security();
securityObject.setAbs(true);
securityObject.setAirbag(true);
List<Car> carList = carEJB.listCarsBySecurity(securityObject);
assertTrue("Should contain at least 1 car with ABS and Airbag", carList.size() > 0 );
for (Car carTemporary : carList) {
System.out.println(carTemporary.toString());
}
}
The thing is that the list does not contain any cars at all. And I think I know why; the named query does try to match the security_id with NULL (since I have not define it).
My question is: How can I perform a query by passing a object as a query parameter with no ID and by not specify all fields which shall be compared inside that object? (or how exclude the ID from a search)?
Best regards

You can define a named query using OR and passing each one of the object's attributes. You can also use Criteria API to build a query based on the fields you want to query about. Since you already have a named query I'll leave that one to you.
If you decide to go that way (tough field by field comparation is kind of insane if your entity has way too many attributes). Using criteria you can do something like this:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Car> query = builder.createQuery(Car.class);
Root<Car> queryRoot = query.from(Car.class);
query.select(queryRoot);
Path<String> pathToYourField = root.get(yourField); //yourField is a variable containing the field.
//You can store all the variables in a list, iterate
//over them and do this for each one.
query.where(builder.and(builder.equal(pathToYourField, "particularValue"))); //You compare the path against a value.
//Rest of the fields / paths
TypedQuery<Car> typedQuery = entityManager.createQuery(query);
List<Car> cars = typedQuery.getResultList();
EDIT: About performance, check this links:
JPA Criteria vs NamedQueries
Another answer regarding Criteria vs HQL
Criteria overhead discussion

Related

How to avoid extra table while joining the two columns in JPA?

I have two classes A & B with as :
This is the class A
class A{
private int id;
private String name ;
private String emailId;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy="aList")
List<B> bList;
// getter and setters
}
This is the class B
class B{
private int id;
private int AId ;
private String location;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
List<A> aList;
// getter and setters
}
And this is the Dao Class method with which I am trying to get the joined Data.
public List<A> getA() {
Session session = entityManagerFactory.unwrap(SessionFactory.class).openSession();
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<A> criteria = builder.createQuery(A.class);
Root<A> root = criteria.from(A.class);
Join<A, B> joinList = root.join("bList", JoinType.LEFT);
try{
Predicate filters = builder.and(builder.equal(joinList.get("AId"),root.get("id")),
builder.equal(root.get("name"), "xxx"));
criteria.where(filters);
List<A> aList = (List<A>)session.createQuery(criteria).getResultList();
return aList;
}catch(Exception e){
e.printStackTrace();
}
return null;
}
But every time it is creating another table and I don't want the extra table to be created rather I want to apply join like we do in mysql i.e without creating any extra table. How it can be done ??
Thanks
If I correctly understood you. JOIN operator is not creating new tables on its own. It is rather the way you described your entity. You have used Many to many relation which created another table. This table is needed to satisfy 3 Normal Form of database.
Solution
Accept existence of another table, until you like to keep consistency of your DB, and prevent data leaks
Redefine your entity so it has Many to one or One to many relation

ListJoin and multiselect with criteriaquery with Polymorphism

I was wondering if anyone knows the cause of the Exception being thrown? I have the following entities below. Is it because some Employees returned aren't DriverEmployees and thus do not have routes?
#Entity
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
private String name;
}
#Entity
public class DriverEmployee extends Employee {
#OneToMany(fetch = FetchType.LAZY)
private List<Routes> routes;
}
#Entity
public class Routes {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
private String name;
private String description;
}
CriteriaQuery<Tuple> criteria = criteriaBuilder.createQuery(Tuple.class);
Root<Employee> employee = criteria.from(Employee.class);
Root<DriverEmployee> driverEmployee = criteriaBuilder.treat(employee, DriverEmployee.class);
ListJoin<DriverEmployee, Routes> routes = driverEmployee.joinList("routes");
// Want routes to be returned as a list in the multiselect.
criteria.multiselect(employee.get("name").alias("name"), routes.alias("routes"));
TypedQuery<Tuple> query = em.createQuery(criteria);
query.getResultList().forEach((t) -> {
process(t.get("name", String.class));
processList(t.get("routes", List.class).size());
});
The error I'm receiving is below. Anyone know how I can get the following to run successfully?
Caused by: java.lang.IllegalStateException: No data type for node: org.hibernate.hql.internal.ast.tree.IdentNode
+-[IDENT] IdentNode: 'routes' {originalText=routes}
In this current example you have tuple which contains one object of DriverEmployee and one object of Routes (I recommend you to rename this entity to Route and set #Table(name = "routes"). According to JavaDoc of multiselect():
If the type of the criteria query is CriteriaQuery (i.e., a criteria query object created by either the createTupleQuery method or by passing a Tuple class argument to the createQuery method), a Tuple object corresponding to the arguments of the multiselect method, in the specified order, will be instantiated and returned for each row that results from the query execution.
So it means that you CAN'T make a Tuple like Tuple<DriverEmployee, List<Routes>>
The way how you can reach such behavior of your current query is to do it by yourself. For instance:
your method with criteria:
CriteriaQuery<Tuple> criteria = criteriaBuilder.createQuery(Tuple.class);
Root<Employee> employee = criteria.from(Employee.class);
Root<DriverEmployee> driverEmployee = criteriaBuilder.treat(employee, DriverEmployee.class);
ListJoin<DriverEmployee, Routes> routes = driverEmployee.joinList("routes");
criteria.multiselect(employee.get("name").alias("name"), routes.alias("routes"));
TypedQuery<Tuple> query = em.createQuery(criteria);
List<Tuple> resultList = query.getResultList();
Map<String, List<Routes>> resultMap = getMapFromResultList(resultList);
resultMap.entrySet().forEach((name, routesList) -> {
process(name);
processList(routesList);
});
and the method of obtaining the map:
private Map<String, List<Routes>> getMapFromResultList(List<Tuple> tuples) {
Map<String, List<Routes>> map = new HashMap<>();
tuples.forEach(tuple -> {
String name = tuple.get("name", String.class);
Routes routes = tuple.get("routes", Routes.class);
map.computeIfPresent(name, (key, value) -> {
value.add(routes);
return value;
});
map.computeIfAbsent(name, (key) -> {
List<Routes> routesList = new ArrayList<>();
routesList.add(routes);
return routesList;
});
});
return map;
}
I guess the Hibernate JPA Criteria implememtation doesn't support that. If you really want to use the JPA Criteria API to do this, you are probably out of luck. In JPQL/HQL you could model this like SELECT e.name, r FROM DriverEmployee e LEFT JOIN e.routes r. On top of that you have to extract the values like Andrew Kolesnyk mentioned.
However, this is is the perfect use case for Blaze-Persistence Entity Views.
Blaze-Persitence is a query builder on top of JPA which supports many of the advanced DBMS features on top of the JPA model. I created Entity Views on top of it to allow easy mapping between JPA models and custom interface defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure the way you like and map attributes(getters) via JPQL expressions to the entity model. Since the attribute name is used as default mapping, you mostly don't need explicit mappings as 80% of the use cases is to have DTOs that are a subset of the entity model.
A mapping for your model could look as simple as the following
#EntityView(DriverEmployee.class)
interface EmployeeDto {
#IdMapping
Integer getId();
String getName();
List<RoutesDto> getRoutes();
}
#EntityView(Routes.class)
interface RoutesDto {
#IdMapping
Integer getId();
String getName();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
EmployeeDto dto = entityViewManager.find(entityManager, EmployeeDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features and it can also be saved back. Here a sample repository
#Repository
interface EmployeeRepository {
EmployeeDto findOne(Long id);
}
It will only fetch the mappings that you tell it to fetch.
Here you can see an example project: https://github.com/Blazebit/blaze-persistence/tree/master/examples/spring-data-webmvc

Merge multiple Spring JPA Page objects results

My initial requirement was to fetch Car details as:
List<String> carMake = getUserCarMake();
Page<Car> carDetails = carRepository.findAllCarsInfo(carMake, pageRequest);
CarRepository.java
#Query(value="SELECT A FROM Cars A WHERE A.model = ?1
public Page<Cars> findAllCarsInfo(List<String> carMake, Pageable pageRequest);
Now my requirement has changed to fetch car details based car models for each car make. So I have changed the code as shown
for (Cars car : userCars) {
String carMake = car.getCarMake();
List<String> carModelForMake = new ArrayList<>();
List <CarModels> carModelList = car.getCarModels();
for (CarModels carModel : carModelList) {
carModelForMake.add(carModel.getModelName());
Page<Car> carDetails = carRepository.findAllCarsInfo(carModelForMake, carMake, pageRequest)
}
}
CarRepository.java
#Query(value="SELECT A FROM Cars A WHERE A.model IN ?1 AND A.make = ?2”
public Page<Car> findAllCarsInfo(List<String> carModel, String carMake,Pageable pageRequest);
So for each car i have a carMake and corresponding carModels for that make which i then pass to the query to fetch carDetails which is a Page Object. As a result same query is called multiple times for different carMake.
The problem is how do I manage the Page object here. In the above scenario the Page object will contain only the details of last car from the carModelList, rest will be overwritten as I do not
have an option of carDetails.addAll() as in case of List.
Note: I cannot use the below query as the model can overlap across different makes.
SELECT A FROM Cars A WHERE A.model IN ?1 AND A.make IN ?2
Also my pageRequest has size as (0, 20 )
I have tried to modify the query to remove pageRequest and use findAll to fetch the results in List and then convert them to PageObject but that breaks the pagination because the page.getContent() has the entire result set and not just 20 records.
Page<Car> carDetails = new PageImpl <>(carDetailsList, pageRequest, carDetailsList.size());
How can I effectively get Page object or merge different page objects here so that my pagination works as it did in my previous requirement.
Sometimes it is a good idea to create a special "query entity" class that includes everything that is needed to respond to a certain kind of client request.
The general idea is like this:
Let's say you'd have two classes in you domain:
#Entity
#Table(name = "table_a")
public class A {
#Id int id;
String propertyA;
int bId;
}
#Entity
public class B {
#Id int id;
String propertyB;
}
And then you'd combine the two two the mentioned "query entity" (outside of the domain).
#Entity
#Table(name = "table_a")
public class QueryEntity {
private #Id private int aId;
private String propertyA;
private B b;
public String propertyA() {
return propertyA;
}
public String propertyB() {
return b.propertyB;
}
}
I'm not quite sure whether this approach is applicable in your case, but hopefully it makes the idea clear.

How to fetch a field lazily with Hibernate Criteria

Each row of the table Person (having name, firstname and age) shall be read.
EntityManager em = emf.createEntityManager();
Session s = (Session) em.getDelegate();
Criteria criteria = s.createCriteria(Person.class);
criteria.setFetchMode("age", FetchMode.SELECT);
But the SQL shows
Hibernate:
select
person0_.name,
person0_.firstname,
person0_.age
from
SCOPE.PERSON person0_
How to let the age be lazy ONLY for the Criteria??
I think that lazy mode only makes sense with associations. If you are accessing a plain table it will load all the fields.
If you want the age field not to appear in the SQL and so not being loaded into memory then use projections:
Criteria crit = session.createCriteria(Person.class);
ProjectionList projList = Projections.projectionList();
projList.add(Projections.property("name"));
projList.add(Projections.property("firstname"));
crit.setProjection(projList);
Setting the FetchMode of the "age" property on a criteria has no effect because the fetching strategy at this point is for associated objects only but not for properties. See section 20.1. Fetching strategies of the hibernate docs.
Hibernate uses a fetching strategy to retrieve associated objects
if the application needs to navigate the association. Fetch strategies
can be declared in the O/R mapping metadata, or over-ridden by a
particular HQL or Criteria query.
The only way for lazy loading of a property is the #Basic annotation set to FetchType.LAZY. See here, or if you use .hbm.xml files for mapping use lazy=true, see this section of the hibernate docs.
The #Basic annotation allows you to declare the fetching strategy for
a property. If set to LAZY, specifies that this property should be
fetched lazily when the instance variable is first accessed. It
requires build-time bytecode instrumentation, if your classes are not
instrumented, property level lazy loading is silently ignored.
Lazy loading of properties also use buildtime bytecode instumentation (hibernate is changing the entity classes after compilation to allow lazy loading of properties). Read 20.1.8. Using lazy property fetching
An other possible solution (except for all the other solutions) to your problem is to make a simpler Person class and use a constructor query like:
public class PersonDTO {
private String name;
private String firstname;
private Person(String name, String firstname) {
this.name = name;
this.firstname = firstname;
}
// getters & setters
}
Query q = session.createQuery("select new your.package.name.PersonDTO("
+ "p.name, p.firstname) from Person p");
q.list();
You could even use your existing Person class, just extend it with an appropriate constructor, but I would prefer explicitness.
But all the solutions presented here do not implement a lazy loading of the age attribute. The only way to do this is the #Basicannotation, or you have to implement your own lazy loading.
If your age is an object like the PersonAge of #Dragan you could associate the fecth mode with the criteria rather than the entity like you do.
So, I think you have three options:
age as primitive and projection like #Paco says (Person.age will be null and not a Proxy, you lose the lazyness that you want)
age as primitive without projection (more bytes in the wire)
age as PersonAge + criteria.setFetchMode (you will get the lazyness that you want at the cost of an extra object/table/mapping)
For Projection you could use ResultTransformer to
Criteria crit = session.createCriteria(Person.class);
ProjectionList projList = Projections.projectionList();
projList.add(Projections.property("name"));
projList.add(Projections.property("firstname"));
crit.setProjection(projList);
crit.setResultTransformer(new ResultTransformer() {
#Override
public Object transformTuple(Object[] tuple, String[] aliases) {
String name = (Long) tuple[0];
String firstName = (String) tuple[1];
return new Person(name , firstName);
}
#Override
public List<Reference> transformList(List collection) {
return collection;
}
});
I think you could create a PersonProxy on your own that triggers a query for retrieve the age but this is kind of awful.
#Override
public Object transformTuple(Object[] tuple, String[] aliases) {
String name = (Long) tuple[0];
String firstName = (String) tuple[1];
return new PersonProxy(name , firstName);
}
class PersonProxy {
Person realPerson;
public getAge(){
// create a query with realPerson.id for retrieve the age.
}
}
Your reasoning is valid (in general; we can however argue about the specific example of the age field), but unfortunately there is no straight-forward solution for this. Actually, Hibernate has the concept of fetch profiles, but it is currently very limited (you can override the default fetch plan/strategy only with the join-style fetch profiles).
So, the possible workaround to your issue could be as follows.
1) Move age to a separate entity and associate the Person entity with it with a lazy one-to-one relationship:
#Entity
class PersonAge {
private Integer age;
}
#Entity
class Person {
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true, optional = false)
#JoinColumn(name = "PERSON_AGE_ID")
private PersonAge personAge;
public Integer getAge() {
return personAge.getAge();
}
public void setAge(Integer age) {
personAge.setAge(age);
}
}
2) Define a fetch profile which overrides the default one:
#FetchProfile(name = "person-with-age", fetchOverrides = {
#FetchProfile.FetchOverride(entity = Person.class, association = "personAge", mode = FetchMode.JOIN)
})
3) Enable this profile for each session in the application:
session.enableFetchProfile("person-with-age");
Depending on the framework you use, there should be an easy hook/interceptor which you will use to enable the profile for each session (transaction) that is craeted. For example, an approach in Spring could be to override AbstractPlatformTransactionManager.doBegin of the transaction manager in use.
This way the personAge will be eagerly loaded in all the sessions in the application, unless the fetch profile is explicitly disabled.
4) Disable the fetch profile in the session in which you use the desired Criteria query:
session.disableFetchProfile("person-with-age");
This way the default fetch plan/strategy is used (specified in the entity mappings), which is the lazy loading of the PersonAge.
You can simply define a new entity SimplePerson mapped to the same persons database table which contains only the following attributes:
id
name
firstName
This way, when selecting a SimplePerson with both Criteria and HQL, the age column will not be retrieved.
Another alternative is to use lazy loading for basic attributes, but mapping multiple subentities to the same database table is much more flexible.
I would like to add (or maybe clarify) the followings. Given the main class (Settlement) with an attribute class (Customer):
#Entity
#Table(name = "settlement")
public class Settlement extends IdBasedObject {
...
#OneToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "customer_id_fk")
private Customer customer;
}
#Entity
#Table(name = "customer", schema = SchemaUtil.SCHEMA_COMMON)
public class Customer extends IdBasedObject {
#Column(name = "organization_type")
#Enumerated(EnumType.ORDINAL)
private CompanyType companyType;
#Column(name = "organization_legal_name")
private String companyLegalName;
...
}
If you would like to get all the distinct customers from the Settlement, you would use the Projections distinct on the 'customer' property and followed by creating an alias from the Settlement class:
public List<Customer> findUniqueCustomer() throws Exception {
Session session = super.getSessionFactory().openSession();
ProjectionList projectionList = Projections.projectionList();
projectionList.add(Projections.distinct(Projections.property("customer")));
Criteria criteria = session.createCriteria(Settlement.class);
criteria.setProjection(projectionList);
criteria.createAlias("customer", "customer");
return criteria.list();
}
Now, if you do that, you will get back a list of non-proxy error 'could not initialize proxy - no Session' for each of the customer object.
Fortunately, criteria provides the setResultTransformer function that will 're-shape' the return.
criteria.setResultTransformer(new ResultTransformer() {
#Override
public Object transformTuple(Object[] tuple, String[] aliases) {
Customer customerObject = (Customer) tuple[0];
Customer customer = new Customer();
customer.setId(customerObject.getId());
customer.setVersion(customerObject.getVersion());
customer.setCompanyType(customerObject.getCompanyType());
customer.setCompanyLegalName(customerObject.getCompanyLegalName());
return customer;
...
}
#SuppressWarnings("rawtypes")
#Override
public List<Customer> transformList(List collection) {
return collection;
}
});
The tuple[0] essentially contains the customer object value, since the customer object is not proxied, you will get the error. In the transformTuple function, you have a chance to 're-create' each of the customer object thereby avoiding the 'non-proxied' error.
Please give a try.

Resolving Criteria on Polymorphic child class attribute jpa hibernate query

Using hibernate 3.6.10 with hibernate jpa 2.0.
My problem boils down to needing to set some criteria on a column of a child object during a somewhat complex joining query.
I have a set of objects similar to:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class Ball
{
private String name;
//...getter and setter crud...
}
#Entity
public class BeachBall extend ball
{
private boolean atTheBeach;
//...getter and setter crud...
}
#Entity
public class SoccerBall extend ball
{
private int numberOfKicks;
//...getter and setter crud...
}
#Entity
public class Trunk
{
private Set<Ball> balls;
#OneToMany(mappedBy = "trunk", cascade = CascadeType.ALL, orphanRemoval = true)
public Set<Ball> getBalls()
{
return balls;
}
}
#Entity
public class Car
{
private Trunk trunk;
private String carModel;
//...getter and setter crud...
}
Now i need to query how many soccer balls have 20 kicks in a car with a specific model.
Using JPA I tried to do something like:
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Car> criteriaQuery = criteriaBuilder.createQuery(Car.class);
Root<Car> car= criteriaQuery.from(Car.class);
Join<Car, Trunk> trunkJoin = car.join(Car_.trunk);
Join<Trunk, Ball> ballJoin = trunkJoin.join(Trunk_.Balls);
criteriaQuery.select(trunk);
Predicate [] restrictions = new Predicate[]{ criteriaBuiler.equal(car.get(carModel), "Civic"), criteriaBuilder.equal(ballJoin.get("numberOfKicks"), 20)};
criteriaQuery.where(restrictions);
TypedQuery<Car> typedQuery = entityManager.createQuery(criteriaQuery);
Car carWithSoccerBalls = typedQuery.getSingleResult();
At runtime the above code dies because numberOfKicks is only on soccerballs and due to how its typed in Trunk it only knows about ball. If i manually create a from on the soccerballs and setup criteria to join it i can query numberOfKicks, however i feel like there must be a way to inform the query that the set is in fact a set.
Please note i cannot post any of the actual code so all above examples are just examples.
Using JPA and hibernate like above is there a way to force hibernate to know that the set< ball > is actually set< soccerball >?
Due to time restrictions i'm taking the easy way out :(. If anyone can answer better then what i have i'll gladly choose their answer over mine.
To make the criteria api recognize that i'm looking for the inherited table i changed my query code to be:
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Car> criteriaQuery = criteriaBuilder.createQuery(Car.class);
Root<Car> car= criteriaQuery.from(Car.class);
Root<Soccerball> soccerballs = criteriaQuery.from(SoccerBall.class);
Join<Car, Trunk> trunkJoin = car.join(Car_.trunk);
Join<Trunk, Ball> ballJoin = trunkJoin.join(Trunk_.Balls);
criteriaQuery.select(trunk);
Predicate [] restrictions = new Predicate[]{ criteriaBuiler.equal(car.get(carModel), "Civic"), criteriaBuilder.equal(soccerball.get("numberOfKicks"),20), criteriaBuilder.equal(soccerball.get(SoccerBall_.id),car.get(Car_.id))};
criteriaQuery.where(restrictions);
TypedQuery<Car> typedQuery = entityManager.createQuery(criteriaQuery);
Car carWithSoccerBalls = typedQuery.getSingleResult();
The following retrieves all Cars with nested list attributes satisfying equality criteria for subclass type in a collection and equality on root element.
I've modified the query to work with the datamodel in the original question.
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Car> carQuery = criteriaBuilder.createQuery(Car.class);
Root<Car> carRoot = carQuery.from(Car.class);
Subquery<SoccerBall> ballQuery = carQuery.subquery(SoccerBall.class);
Root<SoccerBall> soccerBall = ballQuery.from(SoccerBall.class);
ballQuery.select(soccerBall);
ballQuery.where(criteriaBuilder.equal(soccerBall.get(SoccerBall_.numberOfKicks), 25));
Join<Car, Trunk> carTrunkJoin = carRoot.join(Car_.trunk);
SetJoin<Trunk, Ball> trunkBallJoin = carTrunkJoin.join(Trunk_.balls);
carQuery.select(carRoot);
carQuery.where(criteriaBuilder.and(
trunkBallJoin.in(ballQuery),
criteriaBuilder.equal(carRoot.get(Car_.carModel), "Civic")));
TypedQuery<?> typedQuery = entityManager.createQuery(carQuery);
List<?> result = typedQuery.getResultList();
The equivalent SQL is:
SELECT * FROM car JOIN trunk JOIN ball WHERE ball.id IN (SELECT soccerball.id FROM soccerball WHERE soccerball.numberOfKicks = 25)

Categories