JPA Criteria builder for dynamic query - java

I have to write a method where I pass it some arguments and depending on which arguments are empty it modifies the query I need to execute at the end.
E.g. if I want to select from a table Customer that has an ID, name, surname and address the starting query would be SELECT * FROM Customer WHERE ID=myId AND name=myName AND surname=mySurname (and now we check if the address I sent is empty, if it is NOT, add it to the query).
I know how to do this in C#, by simply doing a starting string and then just adding to it and using ExecuteStoreQuery directly, but how would this look in Java? Is Criteria Builder a viable tool for this?
Thank you so much!

Using a CriteriaQuery, it is possible to do something like that (using Static JPA Metamodel Classes):
public List<Customer> findCustomer(Long myId, String myName, String mySurName, String myAddress) {
CriteriaBuilder cb = enitityManager.getCriteriaBuilder();
CriteriaQuery<Customer> query = cb.createQuery(Customer.class);
Root<Customer> root = query.from(Customer.class);
List<Predicate> predicates = new ArrayList<>();
predicates.add(cb.equal(root.get(Customer_.ID), myId));
predicates.add(cb.equal(root.get(Customer_.NAME), myName));
predicates.add(cb.equal(root.get(Customer_.SURNAME), mySurName));
if(address != null) {
predicates.add(cb.equal(root.get(Customer_.ADDRESS), myAddress));
}
query.select(root).where(cb.and(predicates.toArray(new Predicate[0])));
return entityManager.createQuery(query).getResultList();
}
In this case, the address will only be checked if it is not null.
Another example without using Static JPA Metamodel (not 100% sure about this):
public List<Customer> findCustomer(Long myId, String myName, String mySurName, String myAddress) {
CriteriaBuilder cb = enitityManager.getCriteriaBuilder();
CriteriaQuery<Customer> query = cb.createQuery(Customer.class);
Root<Customer> root = query.from(Customer.class);
List<Predicate> predicates = new ArrayList<>();
predicates.add(cb.equal(root.get("ID"), myId));
predicates.add(cb.equal(root.get("name"), myName));
predicates.add(cb.equal(root.get("surename"), mySurName));
if(address != null) {
predicates.add(cb.equal(root.get("address"), myAddress));
}
query.select(root).where(cb.and(predicates.toArray(new Predicate[0])));
return entityManager.createQuery(query).getResultList();
}

Related

Remove duplicates from result set using javax.persistence.criteria by data prorety

I need to return the latest data. Date fields are called dateModified. Since the old data is not deleted in any way, there are a lot of similar records, they differ only in the dateModified field. The current query returns all records, and I need only the most recent in the field dateModified.
public List<SomeDto> search(SearchDto criteria) {
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<SomeDto> query = cb.createQuery(SomeDto.class);
final Root<SomeModel> root = query.from(SomeModel.class);
query.multiselect(
root.get(ID),
root.get(SOME_ENTERNAL_ID),
root.get(SOME_NAME_ID));
query.where(createPredicates(cb, query, root, criteria));
return entityManager.createQuery(query).getResultList();
}
Try to order all the results in descending order and get the first result, something like this:
public List<SomeDto> search(SearchDto criteria) {
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<SomeDto> query = cb.createQuery(SomeDto.class);
final Root<SomeModel> root = query.from(SomeModel.class);
query.multiselect(
root.get(ID),
root.get(SOME_ENTERNAL_ID),
root.get(SOME_NAME_ID));
query.where(createPredicates(cb, query, root, criteria))
.orderBy(cb.desc(root.get("dateModified")));
return entityManager.createQuery(query)
.setFirstResult(0)
.setMaxResults(1)
.getResultList();
}
The method will return a list with one element which has the most recent dateModified.

JPA CriteriaQuery - How to use IN comparison operator

I have two java objects.
User(every user has an index column)
Address( every address has an user_index column too)
I have a List of all the users index list, usersIndexList as given input and I want to fetch all of the address objects based on this usersIndexList. I found an example on another thread. And tried to follow it but it does not work.
JPA CriteriaBuilder - How to use "IN" comparison operator
My code:
List<String> usersIndexList= new ArrayList<String> ();
for (User u : usersList) {
usersIndexList.add(u.getIndex());
}
CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
CriteriaQuery<User> subQuery = criteriaBuilder.createQuery(User.class);
Root<User> fromUser= subQuery.from(User.class);
Expression<String> exp = fromUser.get("user_index");
Predicate predicate = exp.in(usersIndexList);
subQuery.where(predicate);
TypedQuery<User> query = getEntityManager().createQuery(subQuery);
return query.getResultList();
But this query is not returning the desired result :(
Can someone please tell me, where I am doing wrong or give me an alternate solutions if its possible via nativequery or namedquery or any other way
As per your question, you want to fetch all of the address objects based on this usersIndexList. But in your code you are selecting User objects, not the Address. Is my understanding Correct? If yes, then please change your root to Address as below -
List<String> usersIndexList= new ArrayList<String> ();
for (User u : usersList) {
usersIndexList.add(u.getIndex());
}
CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
CriteriaQuery<Address> subQuery = criteriaBuilder.createQuery(Address.class);
Root<Address> fromAddress= subQuery.from(Address.class);
Expression<String> exp = fromAddress.get("user_index");
Predicate predicate = exp.in(usersIndexList);
subQuery.where(predicate);
TypedQuery<Address> query = getEntityManager().createQuery(subQuery);
return query.getResultList();

Java Criteria API: Select single column from one-to-many relationship table

I am trying to select a single column from a related table. I have a table (Item) with many Values. I would like to select Value.valueString.
Basically, the query is supposed to pass in a bunch of values and pull any ValueFields that contain those values. The SQL might look something like this:
select ItemValues.valueString from ItemEntity
join StockItem on ItemEntity.stockItemId = StockItem.id
join ItemValues on ItemEntity.id = ItemValues.itemId
where StockItem.vendor = vendorId
AND (ItemValues.valueString like '%test%' OR ItemValues.valueString like '%test2%'...);
Here is my code:
final CriteriaBuilder builder = this.entityManager.getCriteriaBuilder();
final CriteriaQuery<String> query = builder.createQuery(String.class);
final Root<ItemEntity> root = query.from(ItemEntity.class);
query.select(root.join("ItemValues").<String>get("ValueString"));
final List<Predicate> filters = new LinkedList<Predicate>();
filters.add(builder.equal(root.join("StockItem").get("id"), vendorNumber));
final List<Predicate> filterNamesCriteria = new LinkedList<Predicate>();
if (filenames.length > 0) {
for (String fileName : filenames) {
filterNamesCriteria.add(builder.like(root.join("ItemValues").<String>get("ValueString"), fileName));
}
filters.add(builder.or(filterNamesCriteria.toArray(new Predicate[0])));
}
query.where(filters.toArray(new Predicate[0]));
final TypedQuery<String> resolvedQuery = this.entityManager.createQuery(query);
return resolvedQuery.getResultList();
I want the result to return a List of Strings (valueString column), but it's not returning anything.
Am I doing something wrong? When I say "builder.createQuery(String.class)", is that correct?
I found the problem:
filters.add(builder.equal(root.join("StockItem").get("id"), vendorNumber));
I was joining based on the StockItem id and not the StockItem.itemNumber
I used two queries to solve the issue of joining the Itemvalues map (it was returning 32,000+ results)

Named Query Possibility

I have a fields of customerName, MembershipNumber, nationality and some other field in my customer table and if i get one value from the above three. I need to write only one named query to get the value from the Customer table from the value i got. Is there any possibility to do that by named query without use of normal query in jpa?...
StringBuilder s=new StringBuilder();
s.append("select c from customerdetail c where )
if(customerName!=null)
{
s.append(" c.customerName = :customerName")
}
else if(memberShipNumber!=null)
{
s.append(" c.memberShipNumber = :memberShipNumber")
}
else if(nationality!=null)
{
s.append(" nationality = :nationality)
}
Here i use the same table with three conditions. So is there any possiblity to write only one named query or any other static query to satisfy all the three conditions in jpa?
Try reading ObjectDB's manual on JPA Queries. It provides information on selecting JPA entities and different variations with its custom fields. It has query examples expressed as in JPQL so with use of Criteria. And yes, you can define namedQuery using JPQL and later use-re-use it.
Named queries are static & their scope is persistence context, they can't be altered at runtime.
Below is the sample for adding parameter based on condition using Criteria API.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<CustomerDetail> cq = cb.createQuery(CustomerDetail.class);
Metamodel m = em.getMetamodel();
EntityType<CustomerDetail> CustomerDetail_ = m.entity(CustomerDetail.class);
Root<CustomerDetail> detail = cq.from(CustomerDetail.class);
if(customerName != null)
cq.where(cb.equal(detail.get(CustomerDetail_.customerName), customerName));
if(memberShipNumber != null)
cq.where(cb.equal(detail.get(CustomerDetail_.memberShipNumber), memberShipNumber));
if(nationality != null)
cq.where(cb.equal(detail.get(CustomerDetail_.nationality), nationality));
cq.select(detail);
TypedQuery<CustomerDetail> q = em.createQuery(cq);
List<CustomerDetail> customerList= q.getResultList();
Else, you can go with building a query with string by appending conditions, instead of named query.
You can use projection with NamedQuery, this example will obtain a single customerName field if MembershipNumber is unique or a List with every customerName that matches the where condition:
#Entity
#NamedQuery(name="getCustomerName", query="SELECT c.customerName FROM Customer c WHERE c.membershipNumber = :memNum")
public class Customer {
...
}
Then you can call it with: (em is your EntityManager)
String customerName = em.createNamedQuery("getCustomerName")
.setParameter("memNum", myMembershipNumber)
.getSingleResult();

JPA 2 + Criteria API - Defining a subquery

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;
}

Categories

Resources