JPA 2 + Criteria API - Defining a subquery - java

I try to convert a sql query to Criteria API without success so far. I can create two separate queries which return the values I need, but I don't know how to combine them in a single query.
Here is the sql statement which works:
select company.*, ticketcount.counter from company
join
(select company, COUNT(*) as counter from ticket where state<16 group by company) ticketcount
on company.compid = ticketcount.company;
This Criteria query returns the inner query results:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<intCompany> qTicket = cb.createQuery(intCompany.class);
Root<Ticket> from = qTicket.from(Ticket.class);
Path groupBy = from.get("company");
Predicate state = cb.notEqual(from.<State>get("state"), getStateById(16));
qTicket.select(cb.construct(
intCompany.class, cb.count(from),from.<Company>get("company")))
.where(state).groupBy(groupBy);
em.createQuery(qTicket).getResultList();
In the application I defined a small wrapper/helper class:
public class intCompany{
public Company comp;
public Long opentickets;
public intCompany(Long opentickets,Company comp){
this.comp = comp;
this.opentickets = opentickets;
}
public intCompany(){
}
}
So does anyone has an idea how to get this working?
Update
Thank you. I changed my criteria query as you suggested. I just had to add a loop at the end to get the information I wanted.
List<intCompany> result = em.createQuery(cq).getResultList();
List<Company> cresult = new ArrayList();
for(intCompany ic: result){
ic.comp.setOpentickets(ic.opentickets.intValue());
cresult.add(ic.comp);
}
return cresult;
Maybe it is just not possible to convert the original sql to Criteria API.
Another update
I figured out I had to change the original sql expression to
select company.*, ticketcount.counter from company
left join
(select company, COUNT(*) as counter from ticket where state<16 group by company) ticketcount
on company.compid = ticketcount.company;
Otherwise I do not get companies with no entries in the ticket table.
So are there any other suggestions?

You have almost everything done.
//---//
CriteriaBuilder cb = em.getCriteriaBuilder();
//Your Wrapper class constructor must match with multiselect arguments
CriteriaQuery<IntCompany> cq = cb.createQuery(IntCompany.class);
//Main table
final Root<Ticket> fromTicket= cq.from(Ticket.class);
//Join defined in Ticket Entity
final Path company = fromTicket.get("company");
//Data to select
cq.multiselect(cb.count(from), company);
//Grouping
cq.groupBy(company);
//Restrictions (I don't really understand what you're querying)
Predicate p = cb.lessThan(fromTicket.get("state"), 16);
//You can add more restrictions
// p = cb.and/or(p, ...);
cq.where(p);
List<IntCompany> results = entityManager.createQuery(cq).getResultList();
This should work as expected.

I had similar problem. My solution was to use left outer joins.
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Entity> query = cb.createQuery(Entity.class);
Root<Entity> root = query.from(Entity.class);
Join<Entity,ChildEntity> join = root.join(Entity_.children, JoinType.LEFT);
query.groupBy(root.get( Entity_.id ));
query.select(
cb.construct(
EntityDTO.class,
root.get( Entity_.id ),
root.get( Entity_.name ),
cb.count(join)
));
This JoinType.LEFT guarantees that you will get Entity records (companies) even if it doesn't have any child entities (tickets).
Entity class:
#Entity
public class Entity {
...
#OneToMany(targetEntity = ChildEntity.class, mappedBy = "parent", fetch = FetchType.LAZY, orphanRemoval = false)
private Set<ChildEntity> objects;
...
}
Static model:
#StaticMetamodel( Entity.class )
public class Entity_ {
public static volatile SingularAttribute<Entity, Long> id;
public static volatile SingularAttribute<Entity, String> name;
...
public static volatile SetAttribute<Entity, ChildEntity> objects;
}

Related

JPA2 CriteriaQuery is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause

I have two tables A and B, A has B's foreign key, with the old Criteria API everything works fine however with the CriteriaQuery API I'm getting an error. What different is happening behind the scenes? To me it's logical that these two pieces of code should do the same thing.
Error
Column 'A.PK' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
This works:
Criteria criteria = getSession().createCriteria(A.class);
criteria.setProjection(Projections.groupProperty("b"));
return criteria.list();
this doesn't:
Session session = getSession();
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<A> criteriaQuery = builder.createQuery(A.class);
Root<A> root = criteriaQuery.from(A.class);
criteriaQuery.groupBy(root.get("b"));
return session.createQuery(criteriaQuery).list();
Assuming that you have the following mapping:
#Entity
class A {
#ManyToOne
private B b;
}
#Entity
class B {
#Id
private Long id;
}
Outdated Criteria:
Criteria criteria = getSession().createCriteria(A.class);
criteria.setProjection(Projections.groupProperty("b"));
List<B> result = criteria.list();
actually execute the query like below:
select b_id
from A
group by b_id
and then fetch B's one by one by executing query like this:
select * from B where id = ?
When you use CriteriaBuilder hibernate actually tries to fetch data by one query. To fix the query you can do something like this:
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<B> criteriaQuery = builder.createQuery(B.class);
Root<A> root = criteriaQuery.from(A.class);
Join<A, B> bJoin = root.join("b");
criteriaQuery.groupBy(bJoin.get("id"));
criteriaQuery.select( root.get("b") );
List<B> result = session.createQuery(criteriaQuery).list();

How to query OneToOne entity for specific value with CriteriaQuery

I have an entity (Person) which is a OneToOne to another entity (User). I need to find all Person entities which match User.name using CriteriaQuery.
I can do simple CriteriaQuery for direct attributes of Person just fine:
builder.like(builder.lower(root.get(column)), "%" + pattern.toLowerCase() + "%")
I'm a bit lost on how to do CriteriaQuery queries in this more complex case. From my searches here and elsewhere I think I have to use some kind of Join but I can't get my head wrapped around it.
#Entity()
public class Person extends ModelCore {
#Basic()
private String iD = null;
#OneToOne(cascade = { CascadeType.ALL })
#JoinColumns({ #JoinColumn(name = "T_User") })
private User user = null;
}
#Entity()
public class User extends ModelCore {
#Basic()
private String iD = null;
#Basic()
private String name = null;
}
#Entity()
public class ModelCore{
#Basic()
private Long dbID = null;
}
SOLVED
Nikos's solution works great (thank you!):
String username = ... // the criterion
EntityManager em = ...
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Person> query = cb.createQuery(Person.class);
Root<Person> root = query.from(Person.class);
Join<Person, User> joinUser = root.join("user");
query.where(cb.like(cb.lower(joinUser.get("name")), "%" + username.toLowerCase() + "%"));
Edit 1: Added ModelCore as base class.
Edit 2: Add working solution
Criteria API can be confusing as complexity grows. The first step I always follow is to write down the JPQL query. In this case:
SELECT p
FROM Person p
JOIN User u
WHERE LOWER(u.name) LIKE :username
Translating this to Criteria API is:
// These are initializations
String username = ... // the criterion
EntityManager em = ...
CriteriaBuilder cb = em.getCriteriaBuilder();
// Next line means "the result of the query is Person"
CriteriaQuery<Person> query = cb.createQuery(Person.class);
// This matches the "FROM Person p" part of the JPQL
Root<Person> root = query.from(Person.class);
// This matches the "JOIN User u" part of the JPQL
Join<Person, User> joinUser = root.join("user"); // if you have created the metamodel, adjust accordingly
// This is the "WHERE..." part
query.where(cb.like(cb.lower(joinUser.get("name")), "%" + username.toLowerCase() + "%"));
The WHERE part is confusing because you have to convert the infix SQL/JPQL operators to prefix (i.e. x LIKE y becomes cb.like(x, y)), but the mapping is straightforward.

JPA - Select a field with a calculation

Basically I am trying to select a field where I calculate with using a Postgres postgis query ST_distance_sphere.
Below code allows me to filter out results based on the distance. If the distance is greater than the given value record is filtered. I created the following query with the below predicate builder. However I also want to select the distance value returned by the ST_distance_sphere function as "distance"
Here is how I use JPQL:
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<RestaurantEntity> criteriaQuery = criteriaBuilder.createQuery(RestaurantEntity.class).distinct(true);
Root<RestaurantEntity> restaurantEntityRoot = criteriaQuery.from(RestaurantEntity.class);
Predicate[] searchPredicates = createPredicates(criteriaBuilder, restaurantEntityRoot, restaurantSearch);
List<Order> orders = getSortingOrders(restaurantSearch, criteriaBuilder, restaurantEntityRoot);
criteriaQuery.where(searchPredicates).orderBy(orders);
List<RestaurantEntity> searchResults = entityManager.createQuery(criteriaQuery)
.setMaxResults(restaurantSearch.getSize())
.setFirstResult((restaurantSearch.getPage()) * restaurantSearch.getSize())
.getResultList();
Here is searchByDistance function and JpaRestaurantPredicateBuilder class I use.
class JpaRestaurantPredicateBuilder {
private CriteriaBuilder criteriaBuilder;
private Root<RestaurantEntity> restaurantEntityRoot;
private List<Predicate> predicateList;
JpaRestaurantPredicateBuilder(CriteriaBuilder criteriaBuilder, Root<RestaurantEntity> restaurantEntityRoot) {
this.criteriaBuilder = criteriaBuilder;
this.restaurantEntityRoot = restaurantEntityRoot;
this.predicateList = new ArrayList<>();
}
JpaRestaurantPredicateBuilder searchByDistance(Float lon, Float lat, Long maxDistance) {
Point userLocation = new GeometryFactory().createPoint(new Coordinate(lon, lat));
Join address = this.restaurantEntityRoot.join("addressEntity", JoinType.INNER);
predicateList.add(new WithinDistancePredicate((CriteriaBuilderImpl) criteriaBuilder, address.get("location"), userLocation, maxDistance));
return this;
}
Predicate[] buildArray() {
return predicateList.toArray(new Predicate[0]);
}
}
Here is createPredicates:
private Predicate[] createPredicates(CriteriaBuilder criteriaBuilder, Root<RestaurantEntity> restaurantEntityRoot, RestaurantSearch restaurantSearch) {
return new JpaRestaurantPredicateBuilder(criteriaBuilder, restaurantEntityRoot)
.chainId(restaurantSearch.getChainId())
.searchTerm(retrieveRestaurantIdsForSearchTerm(restaurantSearch.getSearchTerm()))
.cityIdAndDistrictId(restaurantSearch.getCityId(), restaurantSearch.getDistrictId())
.status(restaurantSearch.getStatus())
.searchByDistance(restaurantSearch.getLon(), restaurantSearch.getLat(), restaurantSearch.getMaxDistance())
.buildArray();
}
And here is the generated sql:
select distinct restaurant0_.id
from restaurant restaurant0_
inner join address addressent1_ on restaurant0_.address_id = addressent1_.id
where st_distance_sphere(addressent1_.location, ?) < 1000
order by restaurant0_.id desc
limit ?
Here is the sql I want to generate using JPA (note the line where I select the distance):
select distinct restaurant0_.id,
st_distance_sphere(addressent1_.location, ?) as distance
from restaurant restaurant0_
inner join address addressent1_ on restaurant0_.address_id = addressent1_.id
where st_distance_sphere(addressent1_.location, ?) < 1000
order by restaurant0_.id desc
limit ?
I couldn't find any other answers related with this question. How can I achieve this query using JPA? I tried to use CriteriaQuery's select method to no avail.

JPA CriteriaBuilder Joins with extra ON clause parameters (predicate)

How to join with extra ON clause parameters?
I have an SQL:
select * from Address address left outer join AddressLine line
on line.id = address.lineId AND line.type = 'MA'
where address.id = 1;
I have code:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<AddressResult> query = cb.createQuery(AddressResult.class);
Root<Address> r = query.from(Address.class);
Join<Address, AddressLineMA >linesMA= r.join(Address_.linesMajor, JoinType.LEFT);
To get data from data base.
This query is not working as expected I get SQL like this:
select * from Address address left outer join AddressLine line
on line.id = address.lineId
where address.id = 1;
AND line.type = 'MA' is missing. Does some one knows how to fix this?
My AddressLineMA.class looks like this:
#Entity
#DiscriminatorValue(value = "MA")
public class AddressLineMA extends AddressLine {
}
#Entity
#Table(name = "AddressLine")
#DiscriminatorColumn(name = "TYPE", discriminatorType = DiscriminatorType.STRING)
public abstract class AddressLine {
private Long id;
private String type;
private String line;
}
JPA queries always return all subclasses by default. So if you want only instances of AddressLineMA, you must change your query and relationshipto be on AddressLineMA instead of the inheritance root class, AddressLine.
If there are other subclasses of AddressLineMA that you want to exclude, then you can use the TYPE operator added to JPA 2.0:
query.where(cb.equal(linesMA.type(), package.AddressLineMA.class));
Unfortunately, adding it into the ON clause is only supported in the yet to be released JPA 2.1 spec:
linesMA.on(cb.equal(linesMA.type(), package.AddressLineMA.class));

JPA-2.0 Simple Select-Where question

I am stuck with a problem concerning JPA-2.0 queries with relationships. How would it be possible to select any Dataset with at least one Event with type = B?
#Entity
class Dataset {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "dataset")
public List<Event> events;
}
#Entity
class Event {
#ManyToOne
#JoinColumn
public Dataset dataset;
public Type type;
}
enum Type {
A, B, C
}
My starting point is
CriteriaBuilder _builder = em.getCriteriaBuilder();
CriteriaQuery<Dataset> _criteria = _builder.createQuery(Dataset.class);
// select from
Root<Dataset> _root = _criteria.from(Dataset.class);
_criteria.select(_root);
// apply some filter as where-clause (visitor)
getFilter().apply(
_root, _criteria, _builder, em.getMetamodel()
);
// how to add a clause as defined before?
...
Any ideas on this. I tried to create a subqueries as well as a join, but I somehow did it wrong and always got all datasets as result.
Try
SELECT d FROM DataSet d WHERE EXISTS
(SELECT e FROM Event e WHERE e.dataSet = d and e.type = :type)
EDIT: As Pascal pointed out it looks like you are using the Criteria API. Not as familiar with this, but I'll have a stab.
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Dataset> criteria = builder.createQuery(Dataset.class);
Root<Dataset> root = criteria.from(Dataset.class);
criteria.select(root);
// build the subquery
SubQuery<Event> subQuery = criteria.subQuery(Event.class);
Root<Event> eventRoot = subQuery.from(Event.class);
subQuery.select(eventRoot);
ParameterExpression<String> typeParameter = builder.parameter(String.class);
Predicate typePredicate = builder.equal(eventRoot.get(Event_.type), typeParameter));
// i have not tried this before but I assume this will correlate the subquery with the parent root entity
Predicate correlatePredicate = builder.equal(eventRoot.get(Event_.dataSet), root);
subQuery.where(builder.and(typePredicate, correlatePredicate);
criteria.where(builder.exists(subQuery)));
List<DataSet> dataSets = em.createQuery(criteria).getResultList();
Phew that was hard work. I'm going back to Linq now.

Categories

Resources