JPA 2, join criteria query without entity mapping - java

I have the following tables:
customers
orders
#Entity
public class Customer {
String id;
}
#Entity
public class Order {
String id;
String customerId;
}
I have no use to establish an entity's mapping between those; however I need a query to that should join these two tables:
final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
final CriteriaQuery<Customer> criteriaQuery = criteriaBuilder.createQuery(Customer.class);
final Root<Customer> root = criteriaQuery.from(Customer.class);
final Join<Order, Customer> joinOrder = root.join(Order_.customerId.getName()); // doesn't work
final TypedQuery<Customer> queryData = entityManager.createQuery(
criteriaQuery
.where(
criteriaBuilder.lessThan(root.get(Customer_.creationDate), date)
// should add a predicate with order properties
)
);
return queryData.getResultList();
Is it possible to do something like above with JPA 2 ?

You can use subquery
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Customer> customerQuery =
cb.createQuery(Customer.class);
Root<Customer> customerRoot = customerQuery.from(Customer.class);
Subquery<Order> subQuery = customerQuery.subquery(Order.class);
Root<Order> orderRoot = subQuery.from(Order.class);
//Replace this with the restriction you want to apply to order
Predicate predicate= orderRoot.get("xxxxx").in(xxx, xxx);
subQuery.select(orderRoot.get("customerId")).where(predicate);
customerQuery.select(customerRoot).where(customerRoot.get("id").in(subQuery));
em.createQuery(issueQuery).getResultList();

Related

Convert SQL into CriteriaBuilder statement

Can you help me converting this SQL statement into a CriteriaBuilder statement? The problem I have is with the INNER JOIN statement.
SELECT th.id, th.date, th.exercise_id
FROM traininghistory th
INNER JOIN (
SELECT exercise_id, MAX(date) as maxdate
FROM traininghistory
group by exercise_id
) AS tm on tm.exercise_id = th.exercise_id AND th.date = tm.maxdate
WHERE th.accountid = :accountId
#Entity
public class TrainingHistory {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#NotNull
public Long id;
public Long accountId;
#ManyToOne
public Exercise exercise;
public Date dateDone = new Date();
public WellBeing wellBeing;
public int weight;
public int repetitions;
public int duration;
}
Found a solution by reformulating the query without the INNER JOIN. The following SQL query achieves the same result as the SQL query in the question, but was translatable for me into Criteria API.
FROM traininghistory th
WHERE th.datedone in (
SELECT MAX(tm.datedone)
FROM traininghistory tm
GROUP BY tm.exercise_id
)
AND th.accountid = :userId
So using that as a basis the statement using Criteria API is as follows:
// define query
CriteriaBuilder cb = this.entityManager.getCriteriaBuilder();
CriteriaQuery<TrainingHistory> query = cb.createQuery(TrainingHistory.class);
Root<TrainingHistory> root = query.from(TrainingHistory.class);
query.select(root);
// define subquery
Subquery<Integer> subquery = query.subquery(Integer.class);
Root<TrainingHistory> rootSubquery = subquery.from(TrainingHistory.class);
Expression<Integer> max = cb.max(rootSubquery.get(TrainingHistory_.DATE_DONE));
subquery.select(max);
subquery.groupBy(rootSubquery.get(TrainingHistory_.exercise));
// compose whole query
query.where(
cb.and(
cb.in(root.get(TrainingHistory_.DATE_DONE)).value(subquery),
cb.equal(root.get(TrainingHistory_.ACCOUNT_ID), userId)
)
);
return this.entityManager.createQuery(query).getResultList();

JPA2 Criteria Query to select records in Table A whose reference is not found in Table B

I have the following tables with the following structure
Table A {
id <-- Primary key
someColumn
}
Table B {
id <-- Primary key
someColumn
idOfA <-- Foreign key mapping to Table A
}
Entity classes look like below
#Entity
#Table(name = "A")
public class A implements Serializable {
private static final long serialVersionUID = -78448557049178402L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
.......
.......
#OneToMany(mappedBy = "a")
private List<B> bs = new ArrayList<>();
}
#Entity
#Table(name = "B")
public class B implements Serializable {
private static final long serialVersionUID = -659500557015441771L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
.......
.......
#OneToOne
#JoinColumn(name = "a_id", nullable = false)
private A a;
}
Using JPA2, I want to select records from table A which do not have a reference in Table B.
The Expected native postgres query is
select * from A a
where a.id not in
(select b.idOfA from B b);
What I have so far managed to do is
public List<A> getANotInB() {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
// Select From Table B
CriteriaQuery<B> criteriaQueryB = criteriaBuilder
.createQuery(B.class);
Root<B> rootB = criteriaQueryB.from(B.class);
criteriaQueryB.select(rootB);
// Select records from Table A
CriteriaQuery<A> criteriaQueryA = criteriaBuilder.createQuery(A.class);
Root<A> rootA = criteriaQueryA.from(A.class);
criteriaQueryA.select(A);
// Create predicate
Predicate predicate = rootAttemptA.in(criteriaQueryB.getSelection());
criteriaQueryA.where(criteriaBuilder.not(predicate));
// Create query
TypedQuery<A> query = entityManager.createQuery(criteriaQueryA);
List<A> as= query.getResultList();
System.out.println(as);
return as;
}
I know the code above is incorrect and I have got a lot of basics wong.
Kindly help
Note: I Want to use JPA2 Criteria Query
Try this
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
// Select distinct aid from B
CriteriaQuery<B> bQuery = cb.createQuery(B.class);
Root<B> bRoot = bQuery.from(B.class);
bQuery.select(bRoot.get("a").get("id")).distinct(true);
// Select * from A where aid not in ()
CriteriaQuery<A> aQuery = cb.createQuery(A.class);
Root<A> aRoot = aQuery.from(A.class);
aQuery.select(aRoot).where(cb.not(aRoot.get("id").in(bQuery)));
TypedQuery<A> query = entityManager.createQuery(aQuery);
List<A> result = query.getResultList();
Basically, you will construct part of the query and glue them together.
More information here:
JPA Criteria
I was able to get it done using subquery() as below. Posting it so that it can help others
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
// select a from A a
CriteriaQuery<A> queryA = criteriaBuilder.createQuery(A.class);
Root<A> rootA = queryA.from(A.class);
queryA.select(rootA);
// Select distinct aId from B
CriteriaQuery<B> subQueryB = queryA.subquery(B.class);
Root<B> rootB = subQueryB.from(B.class);
bQuery.select(rootB.get("a")).distinct(true);
queryA.where(criteriaBuilder.not(criteriaBuilder.in(rootA.get("id").value(subQueryB))));
TypedQuery<A> query = entityManager.createQuery(aQuery);
List<A> result = query.getResultList();
Thanks #Mạnh for showing the way

ANY clause on a subquery using jpa criteria API

I have two entities Document and Property where a document has a set of properties:
#Entity
public class Document{
#Id
private Integer id;
#OneToMany
private Set<Property> properties;
}
And
#Entity
public class Property{
#Id
private Integer id;
private String key;
private String value;
}
I want to implement the following JPQL query using Criteria API:
SELECT d FROM Document d
WHERE "value11" = ANY(SELECT p.value FROM d.properties p WHERE p.key="key11")
I tried the following:
EntityManager em = PersistenceManager.INSTANCE.getEntityManager();
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Document> query = criteriaBuilder.createQuery(Document.class);
Root<Document> fromDoc = query.from(Document.class);
Subquery<String> subQuery = query.subquery(String.class);
Root<Property> subRoot = subQuery.from(Property.class);
subQuery.select(subRoot.get(Property_.value));
SetJoin<Document, Property> join = fromDoc.join(Document_.properties, JoinType.INNER);
subQuery.select(join.<String> get("value"));
subQuery.where(criteriaBuilder.equal(join.<String> get("key"), "key11"));
query.where(criteriaBuilder.equal(criteriaBuilder.any(subQuery), "value11"));
Query q = em.createQuery(query);
List<Document> docs = q.getResultList();
PersistenceManager.INSTANCE.close();
But I got this exception:
Exception Description: The query has not been defined correctly, the
expression builder is missing. For sub and parallel queries ensure
the queries builder is always on the left. Query:
ReadAllQuery(referenceClass=Document ) at
org.eclipse.persistence.exceptions.QueryException.invalidBuilderInQuery(QueryException.java:689)
at
org.eclipse.persistence.internal.expressions.SQLSelectStatement.appendFromClauseToWriter(SQLSelectStatement.java:537)
at
org.eclipse.persistence.internal.expressions.SQLSelectStatement.printSQL(SQLSelectStatement.java:1704)
Any help would be appreciated!

JPA Criteria API on ManyToOne optional relationship

I'm trying to do a simple query using JPA Criteria API on following structure
1) Employee
public class Employee {
#Id
#Column(name = "ID", length = 64)
private String id;
#Column(name = "NAME", length = 512)
private String name;
#ManyToOne(optional = true)
#JoinColumn(name = "ORG_ID", nullable = true)
private InternalOrg organization;
}
2) InternalOrg
public class InternalOrg {
#Id
#Column(name = "ID", length = 64)
private String id;
#Column(name = "ORGANIZATION", length = 512)
private String organization;
#Column(name = "CODE", length = 64)
private String code;
}
3) Query
EntityManager em = getEntityManager();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> emp = cq.from(Employee.class);
cq.where(cb.or(emp.get(Employee_.organization).isNull(),
cb.equal(emp.get(Employee_.organization).get(InternalOrg_.code), "1")));
return em.createQuery(cq).getResultList();
As you can see "organization" attribute on Employee is optional. What I'm trying to do is a query using criteria API that returns all records where "employee.organization" is NULL or "employee.organization.code" is equal to a parameter. How do I proceed?
I did some tests and realized that if I change from this:
cq.where(cb.or(emp.get(Employee_.organization).isNull(),
cb.equal(emp.get(Employee_.organization).get(InternalOrg_.code), "1")));
To this:
cq.where(cb.or(emp.get(Employee_.organization).isNull()));
It works but only returns records where organization is NULL.
If I change to this:
cq.where(cb.equal(emp.get(Employee_.organization).get(InternalOrg_.code), "1"));
Records where employee.organization is NULL are ignored.
How do I return employees which organization satisfies criteria AND employees where organization IS NULL?
Thanks in advance,
finally found the solution.
The only way to create get desired result is to fetch (JoinType.LEFT) relationship earlier, here is the final criteria query:
EntityManager em = getEntityManager();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> emp = cq.from(Employee.class);
emp.fetch(Employee_.domain, JoinType.LEFT);
cq.where(cb.or(emp.get(Employee_.organization).isNull(),
cb.equal(emp.get(Employee_.organization).get(InternalOrg_.code), "1")));
return em.createQuery(cq).getResultList();
Thank you for support!
Conditions that are set by calling the CriteriaQuery.where method can restrict the results of a query on the CriteriaQuery object. Calling the where method is analogous to setting the WHERE clause in a JPQL query.
Example:
EntityManager em = ...;
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> emp = cq.from(Employee.class);
cq.where(emp.get(Employee_.organization).isNull());
To specify multiple conditional predicates, use the compound predicate methods (and/or/not) of the CriteriaBuilder interface.
cq.where(emp.get(Employee_.organization).isNull())
.or(cb.eq(emp.get(Employee_.organization.code), "ABC"));
Update:
Try this:
cq.where(
cb.or(
cb.isNull(emp.get(Employee_.organization)),
cb.equal(emp.get(Employee_.organization).get(InternalOrg_.code), "1")));

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