I'm having issues properly grouping my query using CriteriaBuilder and Predicates.
I want to create a query that can generate something like:
SELECT * from tableName where columnA = '1234' and (columnB Like '%33%' or columnB Like '%44%')
But what I'm getting (Obviously expected looking at the code) is:
SELECT * from tableName where columnA = '1234' and columnB Like '%33%' or columnB Like '%44%'
Which does not produce the same result as the first query.
Been trying to work around it, but this is my first time working with criteriaBuilder and predicates.
Here's the code:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Foo> criteriaQuery = builder.createQuery(Foo.class);
Root r = criteriaQuery.from(Foo.class);
Predicate predicate = builder.conjunction();
for(Map.Entry<String, String> entry : searchParams.entrySet()){
if((entry.getKey().equalsIgnoreCase("columnK") || entry.getKey().equalsIgnoreCase("columnY") ||
entry.getKey().equalsIgnoreCase("columnZ") || entry.getKey().equalsIgnoreCase("columnJ")) && !Strings.isNullOrEmpty(entry.getValue())){
predicate = builder.and(predicate,
builder.like(r.get(entry.getKey()),
String.format("%%%s%%", entry.getValue().toString())));
}
else if(entry.getKey().equalsIgnoreCase("theDate") && !Strings.isNullOrEmpty(entry.getValue())){
predicate = builder.and(predicate, builder.equal(r.get(entry.getKey()), entry.getValue()));
}
}
//here's where the problem is ... I realize I can use IN, but I also didn't get that to work
boolean isFirstDone = false;
for(String oneId: idStringList){
if(!isFirstDone) {
predicate = builder.and(predicate, builder.equal(r.get("acceptorId"), oneId));
isFirstDone = true;
}
else{
predicate = builder.or(predicate, builder.equal(r.get("acceptorId"), oneId));
}
}
criteriaQuery.where(predicate);
Thank you.
Okay, so I just cracked this after carefully reading this answer https://stackoverflow.com/a/9323183/5038073 The solution is not exactly what I needed but it helped... a lot
I created a path and combined it with my predicate.
Here's what changed:
This:
boolean isFirstDone = false;
for(String oneId: idStringList){
if(!isFirstDone) {
predicate = builder.and(predicate, builder.equal(r.get("acceptorId"), oneId));
isFirstDone = true;
}
else{
predicate = builder.or(predicate, builder.equal(r.get("acceptorId"), oneId));
}
}
became:
Path<Object> path = r.get("acceptorId");
CriteriaBuilder.In<Object> in = builder.in(path);
for (String oneId: idStringList) {
in.value(oneId);
}
predicate = builder.and(predicate, in);
Pretty simple for all the trouble it took me to solve.
Hope this helps someone else too!
I have two tables that looks like:
Table_0
ID Description
5 description5
6 description6
7 description7
8 description8
Table_1
ID Table_0_ID Status
1 5 LOADED
2 6 LOADED
3 7 LOADED
4 7 LOADED
Table_2
ID Table_1_ID
1 1
2 2
3 3
4 4
my expected result when I invoke findAll query is that it eliminates duplicates so result will return (for both tables) three rows (table_1 id from 1 to 3).
I wrote both named query and criteria builder query but the last one seems to be by 4 times faster. I wonder why. Am I making a mistake?
Here is the code:
query = "SELECT OBJECT(sb) FROM Table_2 sb WHERE sb.Table_1.id IN (SELECT MAX(maxsr.id) FROM Table_1 maxsr WHERE maxsr.status = LOADED GROUP BY maxsr.Table_0.id)")
criteria builder:
final EntityManager em = getEntityManager();
final CriteriaBuilder cb = em.getCriteriaBuilder();
final CriteriaQuery<Table_2> criteriaQuery = cb.createQuery(Table_2.class);
final Root<Table_2> root = criteriaQuery.from(Table_2.class);
Predicate p = getPredicateOnList(data, cb, root, gateSession);
if (p != null) {
criteriaQuery.where(p);
}
final Query q = getEntityManager().createQuery(criteriaQuery);
return q.getResultList();
}
method getPredicateOnList
private Predicate getPredicateOnList(final PaginationData data, final CriteriaBuilder cb, final Root<Table_2> root) {
final Join<Table_2, Table_1> readingJoin = root.join("Table_1");
boolean filterUnloaded = false;
boolean selectMax = true;
for (KeyValuePair pair : data.getRequestParams()) {
if (pair.getKey().equalsIgnoreCase("filterUnloaded")) {
filterUnloaded = ParsingUtils.parseBoolean(pair.getValue(), false);
}
if (pair.getKey().equalsIgnoreCase("selectMax")) {
selectMax = ParsingUtils.parseBoolean(pair.getValue(), true);
}
}
Predicate predicate = null;
if (selectMax) {
List<Long> maxReadingIds = getMaxReadingIds(gateSession.getId(), filterUnloaded);
if (maxReadingIds == null || maxReadingIds.isEmpty()) {
//do nothing
} else {
predicate = readingJoin.get("id").in(maxReadingIds);
}
}
return predicate;
}
method getMaxReadingIds
private List<Long> getMaxReadingIds(Long sessionId, boolean filterUnloaded) {
final CriteriaBuilder cb = em.getCriteriaBuilder();
final CriteriaQuery<Long> maxReadingIdQuery = cb.createQuery(Long.class);
final Root<Table_1> Table_1Root = maxReadingIdQuery.from(Table_1.class);
final Path<Long> idGet = Table_1Root.get("id");
maxReadingIdQuery.select(cb.greatest(idGet));
final Join<Table_1, Table_0> join = Table_1Root.join("Table_0");
maxReadingIdQuery.groupBy(join.get("id"));
Predicate predicate = null;
if (filterUnloaded) {
predicate = cb.equal(Table_1Root.get("status"), LOADED);
}
//omiss sessionId parameter
if (predicate != null) {
maxReadingIdQuery.where(predicate);
}
final Query q = getEntityManager().createQuery(maxReadingIdQuery);
return q.getResultList();
}
(I made some semplification, if code is not well defined please tell me and I supply more information)
Both results are correct but criteria builder is faster.
I am trying to generate query with multiple brackets in 'or' and 'and' conditions but internal brackets are not getting generated.
Brackets for outer predicates are generating properly but not for internal ones.
Code:
CriteriaBuilder criteriaBuilder = this.getEntityManager().getCriteriaBuilder();
CriteriaQuery<ConfigurationKey> query = criteriaBuilder.createQuery(ConfigurationKey.class);
Root<ConfigurationKey> configurationKeyRoot = query.from(ConfigurationKey.class);
Join<ConfigurationKey, Customer> configurationKeyCustomerJoin = configurationKeyRoot.join(ConfigurationKey_.customer);
final List<Predicate> predicates = new ArrayList<>();
predicates.add(criteriaBuilder.equal(configurationKeyCustomerJoin.get(Customer_.externalId), customerId));
predicates.add(criteriaBuilder.equal(configurationKeyRoot.get(ConfigurationKey_.configType), configType));
final List<Predicate> orPredicates = new ArrayList<>();
keys.forEach((x, y) -> {
orPredicates.add(
criteriaBuilder.and(
criteriaBuilder.equal(configurationKeyRoot.get(ConfigurationKey_.keyType), x),
criteriaBuilder.equal(configurationKeyRoot.get(ConfigurationKey_.keyValue), y)
)
);
}
);
predicates.add(criteriaBuilder.or(orPredicates.toArray(new Predicate[orPredicates.size()])));
query.where(criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()])));
query.select(configurationKeyRoot);
TypedQuery<ConfigurationKey> typedQuery = this.getEntityManager().createQuery(query);
Generated Query:
select
configurat0_.id as id1_1_,
configurat0_.createdAt as createdA2_1_,
configurat0_.updatedAt as updatedA3_1_,
configurat0_.configType as configTy4_1_,
configurat0_.customerId as customer7_1_,
configurat0_.keyType as keyType5_1_,
configurat0_.keyValue as keyValue6_1_
from
configuration_keys configurat0_
inner join
customers customer1_
on configurat0_.customerId=customer1_.id
where
customer1_.externalId=?
and configurat0_.configType=?
and (
configurat0_.keyType=?
and configurat0_.keyValue=?
or configurat0_.keyType=?
and configurat0_.keyValue=?
)
Expected Query:
select
configurat0_.id as id1_1_,
configurat0_.createdAt as createdA2_1_,
configurat0_.updatedAt as updatedA3_1_,
configurat0_.configType as configTy4_1_,
configurat0_.customerId as customer7_1_,
configurat0_.keyType as keyType5_1_,
configurat0_.keyValue as keyValue6_1_
from
configuration_keys configurat0_
inner join
customers customer1_
on configurat0_.customerId=customer1_.id
where
customer1_.externalId=?
and configurat0_.configType=?
and (
(configurat0_.keyType=?
and configurat0_.keyValue=?)
or
(configurat0_.keyType=?
and configurat0_.keyValue=?)
)
Please point any mistake.
Specifications<PcPlacement> specification = Specifications.where(null);
Specifications<PcPlacement> specificationInner = Specifications.where(null);
specificationInner = specificationInner.or(buildReferringEntitySpecificationWithContains(
criteria.getUserFullName(), PcPlacement_.pcUser, PcUser_.fullName));
specificationInner = specificationInner.or(buildReferringEntitySpecificationWithContains(
criteria.getUserEmailId(), PcPlacement_.pcUser, PcUser_.emailId));
specification = specification.and(specificationInner);
i want add some more complicated selections as below on date coloumn
select TO_DATE ('04-JAN-2015','DD-MM-YYYY'), to_char(TO_DATE ('04-JAN-2015','DD-MM-YYYY'), 'FMDAY') day,
(case when to_char(trunc(TO_DATE ('04-JAN-2015','DD-MM-YYYY'), 'mm'), 'FMDAY') = 'SUNDAY' then to_number(to_char(TO_DATE ('04-JAN-2015','DD-MM-YYYY'), 'W'))
else ceil((to_char(TO_DATE ('04-JAN-2015','DD-MM-YYYY'), 'dd') + 1 - to_char(next_day(trunc(TO_DATE ('04-JAN-2015','DD-MM-YYYY'), 'mm'), 'SUNDAY'), 'dd'))/7)
end)+1 week_no
from dual
here breakupFields is list of string which leads to date column
final QueryBuilder distinctDateTimeFilter = new QueryBuilder() {
final List<String> breakupFields = new ArrayList<String>(fields.length);
for (final String f : fields)
if (!Strings.isEmpty(f)) {
breakupFields.add(f);
}
}
final QueryBuilder distinctDateTimeFilter = new QueryBuilder() {
#Override
public CriteriaQuery buildQuery(CriteriaBuilder cb, CriteriaQuery query, Predicate p, List orders, Root rt) {
Expression selection = null;
selection = cb.function("TO_CHAR", String.class, cb.sum(CriteriaQueryUtils.getPath(rt, breakupFields), cb.literal(miliSecToAdd)),
cb.literal("W-MM-YYYY"));
return query.select(selection).distinct(true).where(p).orderBy(orders);
}
}
final List<Object> objs = new ArrayList<Object>(ds.executeQuery(classObject, Object.class, distinctDateTimeFilter, ef, session,
SuperUserSecurityContext.class));
Can any one suggest how to add
(case when to_char(trunc(TO_DATE ('04-JAN-2015','DD-MM-YYYY'), 'mm'), 'FMDAY') = 'SUNDAY' then to_number(to_char(TO_DATE ('04-JAN-2015','DD-MM-YYYY'), 'W'))
else ceil((to_char(TO_DATE ('04-JAN-2015','DD-MM-YYYY'), 'dd') + 1 - to_char(next_day(trunc(TO_DATE ('04-JAN-2015','DD-MM-YYYY'), 'mm'), 'SUNDAY'), 'dd'))/7)
end)+1 to criteria builder
You can introduce a column in your Entity marked as
#Formula(value="the expression")
See more here and use the column in the queries.
Or you can define native SQL query with the desired entity class mapped and use fuly native SQL.
I'm developing software which allows a user to select dynamically one or several users from a database using jpa/Criteria.
Edits/updates:
-I now use a growing predicate with ands. The problem is that i'm not indicating the right field but instead just a string in which java is looking for what th user has entered.
-I still have to check how i can work with pathes as i've been advised to do.
When an user inputs d as PC_Name, d as Name and c as Vorname, the verbose eclipselink shows me this:
[EL Fine]: sql: 2012-07-25 15:44:13.173--ServerSession(24105143)--Connection(13480046)--Thread(Thread[main,5,main])--SELECT PERSONALNUMMER, GEBURTSTAG, GRUPPE, IP, MOBIL, NAME, PC_NAME, TELEFON, VORNAME FROM MITARBEITERTABELLE WHERE ((? LIKE ? AND ? LIKE ?) AND ? LIKE ?)
bind => [PC_Name, %d%, Name, %b%, Vorname, %c%]
This confirms what i'm saying, when i input n as name, i get the entire database because n always belongs to name..
End of edits/updates
Here is the sql version which works:
try{
String selectString ="SELECT * FROM mitarbeitertabelle WHERE ";
selectString=StringModulierung(selectString);
PreparedStatement selectMitarbeiter = con.prepareStatement(selectString);
int i =1;
if(isNameSuche()){selectMitarbeiter.setString (i,this.wc+_Name+this.wc); i++;}
if(isVornameSuche()){ selectMitarbeiter.setString (i, this.wc+_Vorname+this.wc); i++;}
if(isPersonalnummerSuche()){selectMitarbeiter.setString (i, this.wc+_Personalnummer+this.wc); i++;}
if(isPC_NameSuche()){selectMitarbeiter.setString (i, this.wc+_PC_Name+this.wc); i++;}
if(isIPSuche()){selectMitarbeiter.setString (i, this.wc+_IP+this.wc); i++;}
if(isTelefonSuche()){ selectMitarbeiter.setString (i, this.wc+_Telefon+this.wc); i++;}
if(isGeburtstagSuche()){selectMitarbeiter.setString (i, this.wc+_Geburtstag+this.wc); i++;}
if(isGruppeSuche()){ selectMitarbeiter.setString (i, this.wc+_Gruppe+this.wc); i++;}
if(isMobilSuche()){ selectMitarbeiter.setString (i, this.wc+_Mobil+this.wc); i++;}
System.out.println(selectMitarbeiter.toString());
ResultSet ergebnis=selectMitarbeiter.executeQuery();`
with:
public String StringModulierung(String str){
boolean erster=true;
if(isNameSuche()){ str=str.concat("Name LIKE ? ");
erster=false;
}
if(isVornameSuche()){ if(erster){str=str.concat("Vorname LIKE ? ");erster=false;
}else{str=str.concat("AND Vorname LIKE ? ");}
}
if(isPersonalnummerSuche()){ if(erster){str=str.concat("Personalnummer LIKE ? ");erster=false;
}else{str=str.concat("AND Personalnummer LIKE ? ");}
}
if(isPC_NameSuche()){ if(erster){str=str.concat("PC_Name LIKE ? ");erster=false;
}else{str=str.concat("AND PC_Name LIKE ? ");}
}
if(isIPSuche()){ if(erster){str=str.concat("IP LIKE ? ");erster=false;
}else{str=str.concat("AND IP LIKE ? ");}
}
if(isTelefonSuche()){ if(erster){str=str.concat("Telefon LIKE ? ");erster=false;
}else{str=str.concat("AND Telefon LIKE ? ");}
}
if(isGeburtstagSuche()){ if(erster){str=str.concat("Geburtstag LIKE ? ");erster=false;
}else{str=str.concat("AND Geburtstag LIKE ? ");}
}
if(isGruppeSuche()){ if(erster){str=str.concat("Gruppe LIKE ? ");erster=false;
}else{str=str.concat("AND Gruppe LIKE ? ");}
}
if(isMobilSuche()){ if(erster){str=str.concat("Mobil LIKE ? ");erster=false;
}else{str=str.concat("AND Mobil LIKE ? ");}
}
return str;
}
Here's what i've done with jpa:
CriteriaBuilder cb = EM.getCriteriaBuilder();
CriteriaQuery<Mitarbeiter2> q = cb.createQuery(Mitarbeiter2.class);
Root<Mitarbeiter2> mit= q.from(Mitarbeiter2.class);
ParameterExpression<String> pc_name = cb.parameter(String.class,"PC_Name_p");
ParameterExpression<String> ip = cb.parameter(String.class,"IP_p");
ParameterExpression<String> pers_num = cb.parameter(String.class,"Personalnummer_p");
ParameterExpression<String> name = cb.parameter(String.class,"Name_p");
ParameterExpression<String> vorname = cb.parameter(String.class,"Vorname_p");
ParameterExpression<String> telefon = cb.parameter(String.class,"Telefon_p");
ParameterExpression<String> geburtstag = cb.parameter(String.class,"Geburtstag_p");
ParameterExpression<String> gruppe = cb.parameter(String.class,"Gruppe_p");
ParameterExpression<String> mobil = cb.parameter(String.class,"Mobil_p");
Predicate p = cb.conjunction();
if(isPC_NameSuche()){
p = cb.and(p, cb.like(pc_name, this.getWc()+_PC_Name+this.getWc() ));
}
if(isIPSuche()) {
p = cb.and(p, cb.like(ip,this.getWc()+_IP+this.getWc()));
}
if(isPersonalnummerSuche()) {
p = cb.and(p, cb.like(pers_num, this.getWc()+_Personalnummer+this.getWc() ));}
if(isNameSuche()){
p = cb.and(p, cb.like(name, this.getWc()+_Name+this.getWc()));
}
if(isVornameSuche()) {
p = cb.and(p, cb.like(vorname, this.getWc()+_Vorname+this.getWc()));}
if(isTelefonSuche()) {
p = cb.and(p, cb.like(telefon, this.getWc()+_Telefon+this.getWc() ));}
if(isGeburtstagSuche()){
p = cb.and(p, cb.like(geburtstag, this.getWc()+_Geburtstag+this.getWc()));}
if(isGruppeSuche()) {
p = cb.and(p, cb.like(gruppe, this.getWc()+_Gruppe+this.getWc()));}
if(isMobilSuche()) {
p = cb.and(p, cb.like(mobil, this.getWc()+_Mobil+this.getWc()));}
q.where(p);
q.select(mit);
TypedQuery<Mitarbeiter2> tq = EM.createQuery(q);
tq.setParameter("PC_Name_p", "PC_Name"); // searches this.getWc()+_Mobil+this.getWc() in PC_Name !
tq.setParameter("IP_p", "IP");
tq.setParameter("Personalnummer_p", "Personalnummer");
tq.setParameter( name/**entered by user*/, "Name"/**should be the field */);
tq.setParameter("Vorname_p", "Vorname");
tq.setParameter("Telefon_p", "Telefon");
tq.setParameter("Geburtstag_p","Geburtstag" );
tq.setParameter("Gruppe_p", "Gruppe");
tq.setParameter("Mobil_p" ,"Mobil");
List<Mitarbeiter2> ergebnis= tq.getResultList();
I've incorporated the two following solutions:
In order to add dynamically where conditions, you can either use a
List where each Predicate is defined like this:
> Predicate p = cb.like(...);
or you can modify dynamically a single Predicate like this:
> Predicate p = cb.conjunction(); for ( filter : filters) {
> p = cb.and(p, cb.like(...)); }
and
When you do q.where you set the WHERE expression.
It does not append, meaning, the last q.where you invoked is the one
set for the query.
What you need to do is build a Boolean Expression (I think ANDs is
what you want).
Then at the and user q.where to set the expression.
Thanks for your help ;) .
Harald
The two problems i had were these:
1) How to build dynamically a query with Criteria , in order to get a query like (in SQL) this(with many different predicates)?
SELECT * FROM mitarbeitertabelle where (1) like (2) and (3) like (4) ...
This means that at runtime, the user can select one or several criteria he'll use to search an mitarbeiter(german for employee).
2)How to set a link to the attributes of my object?
1) In order to do this, you have to build a predicate, using a predicate which is modified at each adding of a new predicate if the criterion is selected.
p= criteriabuilder.and(p, criteriabuilder.like(path object linking to the right attribute, String s used for the search));
2) The right path enables us to have a link to the right attribute. I use it above. Here, the thing is that without pathes, it is not possible to have a link to your attributes so far i know ;) .
After that, you simply have to achieve your sql command by adding the where clause which takes the predicate as a parameter and the select clause which takes the root(where we'll search in the database) as a parameter:
q.where(p);
q.select(mit);
Then , get your results:
List<Mitarbeiter2> ergebnis= EM.createQuery(q).getResultList();
Theere is no need to fill and use parameterexpression objects, because i am not using parameterexpression objects. The string used to search is set directly in the like clause at runtime execution.
The entire code(with my variables,names,..) is:
CriteriaBuilder cb = EM.getCriteriaBuilder();
CriteriaQuery<Mitarbeiter2> q = cb.createQuery(Mitarbeiter2.class);
Root<Mitarbeiter2> mit= q.from(Mitarbeiter2.class);
Path<String> pc_name2 = mit.get("PC_Name");
Path<String> ip2 = mit.get("IP");
Path<String> pers_num2 = mit.get("Personalnummer");
Path<String> name2 = mit.get("Name");
Path<String> vorname2 = mit.get("Vorname");
Path<String> telefon2 = mit.get("Telefon");
Path<String> geburtstag2 = mit.get("Geburtstag");
Path<String> gruppe2 = mit.get("Gruppe");
Path<String> mobil2 = mit.get("Mobil");
Predicate p = cb.conjunction();
if(isPC_NameSuche()){
p = cb.and(p, cb.like(pc_name2, this.getWc()+_PC_Name+this.getWc() ));
}
if(isIPSuche()) {
p = cb.and(p, cb.like(ip2,this.getWc()+_IP+this.getWc()));
}
if(isPersonalnummerSuche()) {
p = cb.and(p, cb.like(pers_num2, this.getWc()+_Personalnummer+this.getWc() ));}
if(isNameSuche()){
p = cb.and(p, cb.like(name2, this.getWc()+_Name+this.getWc()));
}
if(isVornameSuche()) {
p = cb.and(p, cb.like(vorname2, this.getWc()+_Vorname+this.getWc()));}
if(isTelefonSuche()) {
p = cb.and(p, cb.like(telefon2, this.getWc()+_Telefon+this.getWc() ));}
if(isGeburtstagSuche()){
p = cb.and(p, cb.like(geburtstag2, this.getWc()+_Geburtstag+this.getWc()));}
if(isGruppeSuche()) {
p = cb.and(p, cb.like(gruppe2, this.getWc()+_Gruppe+this.getWc()));}
if(isMobilSuche()) {
p = cb.and(p, cb.like(mobil2, this.getWc()+_Mobil+this.getWc()));}
q.where(p);
q.select(mit);
List<Mitarbeiter2> ergebnis= EM.createQuery(q).getResultList();
Thank you very much for your help #perissf and #aviram-segal ! Don't hesitate to comment if you think something isn't easy to understand in my answer.
When you do q.where you set the WHERE expression.
It does not append, meaning, the last q.where you invoked is the one set for the query.
What you need to do is build a Boolean Expression (I think ANDs is what you want).
Then at the and user q.where to set the expression.
In your equal clauses, you are using this expression:
ParameterExpression<String> pc_name = cb.parameter(String.class,"PC_Name");
Try instead this:
Path<Mitarbeiter2> pc_name = mit.get("PC_Name");
Or, using Metamodel generated classes,
Path<Mitarbeiter2> pc_name = mit.get(Mitarbeiter2_.pc_name);
I cannot test the Path expressions right now, but I am sure that the following works:
q.where(cb.like(mit.get("PC_Name"), pc_name));
or:
q.where(cb.like(mit.get(Mitarbeiter2_.pc_name), pc_name));
In order to add dynamically where conditions, you can either use a List<Predicate> where each Predicate is defined like this:
Predicate p = cb.like(...);
or you can modify dynamically a single Predicate like this:
Predicate p = cb.conjunction();
for ( filter : filters) {
p = cb.and(p, cb.like(...));
}
Finally, if you are comparing strings, don't forget to transform everything to the same case, before doing the comparison.