I am triying to do a "like query" with a variable set of strings, in order to retrieve in a single query all texts that contains a set of words, that is:
public long countByTextLike(Set<String> strings) {
CriteriaBuilder builder = manager.getCriteriaBuilder();
CriteriaQuery<Long> query = builder.createQuery(Long.class);
Root<Example> root = query.from(Example.class);
query.select(builder.count(root.get("id"))).where(
builder.and(
builder.equal(root.get("lang"), "EN")
)
);
//this does not work
for (String word : strings) {
query.where(builder.or(builder.like(root.get("text"), word)));
}
return manager.createQuery(query).getSingleResult();
}
unfortunately this does not work because the where is overwritten in each loop. Only the last word of loop is used and "AND" restictions are being overwriten.
How is possible to do a "like query" with a variable number of strings? It is not posible?
I am using the spring framework but i think that the question could be extendable to hibernate
You can use predicates, and then add them all with only one where clause
public long countByTextLike(Set<String> strings) {
CriteriaBuilder builder = currentSession().getCriteriaBuilder();
CriteriaQuery<Long> query = builder.createQuery(Long.class);
Root<Example> root = query.from(Example.class);
Predicate[] predicates = new Predicate[strings.size()];
query.select(builder.count(root.get("id")));
Predicate langPredicate = builder.equal(root.get("lang"), "EN");
int cont = 0;
for (String word : strings) {
Predicate pred = builder.like(root.get("text"), "%" + word + "%");
predicates[cont++] = pred;
}
Predicate orPredicate = builder.or(predicates);
Predicate finalPredicate = builder.and(orPredicate, langPredicate);
return manager.createQuery(query).where(finalPredicate).getSingleResult();
}
Related
I have Spring Boot application where I am doing filtering. User can filter ads through tags(ads-tags is many-to-many with third table). And everything is okay, but when I send list of tags for filtering, my query is returning me a list of all ads which have AT LEAST 1 tag, instead returning only ads which have all tags from filter. If I make query with only one tag, everything is okay, but when I send a list case above is happening. This is my filter method:
#Override
public List<AdsDTO> findAll(AdsSubGroup adssubgroup, Long userId, String status, String adsType,
String businessType, Long adsGroupId, String region, Integer fromPrice,
Integer toPrice, Boolean fixedPrice, Boolean freeDelivery, Boolean productWarranty,
Boolean urgentSales, Boolean hasImage, Integer pageNumber, Integer pageSize, List<String> tags) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Ads> query = builder.createQuery(Ads.class);
Root<Ads> ads = query.from(Ads.class);
// query.orderBy(builder.desc(ads.get("adsDate")));
List<Predicate> predicates = new ArrayList<>();
Join<Ads, JwtUser> adsUsersJoin = ads.join("users");
Join<Ads, AdsSubGroup> adsAdsSubGroupJoin = ads.join("adssubgroup");
Join<Ads, Tag> tagsJoin = ads.join("adsTags");
In<String> in = builder.in(tagsJoin.get("name"));
if (tags != null && tags.size() > 0) {
for (String tag : tags) {
in.value(tag);
}
predicates.add(in);
}
query.select(ads);
query.distinct(true);
query.where(predicates.toArray(new Predicate[0]));
if(!(pageNumber==null && pageSize==null)) {
TypedQuery<Ads> typedQuery = em.createQuery(query);
typedQuery.setFirstResult((pageNumber-1)*pageSize);
typedQuery.setMaxResults(pageSize);
List<Ads> adsList = typedQuery.getResultList();
return AdsConverter.convertToAdsDTO(adsList);
}else {
List<Ads> adsList = em.createQuery(query).getResultList();
return AdsConverter.convertToAdsDTO(adsList);
}
}
How can I fix that query returns me only ads which have every tag from the list?
The problem is in the approach, the query is wrong.
You should for example use a subquery to check that there are N entries for each article, where N is the number of TAGS entered.
Something like that:
//Subquery with count
Subquery<Long> countTagsSq = query.subquery(Long.class);
Root<Ads> rootSQ = countTagsSq.from(Ads.class);
Join<Ads,Tag> joinTagSQ = rootSQ.join("adsTags");
//We set the where condition by Ads id (I don't know what the attribute is called)
countTagsSq.where(
cb.and(
builder.equal(rootSQ.get("id"),ads.get("id")),
joinTagSQ.get("name").in(tags)
)
);
countTagsSq.select(builder.count(joinTagSQ ));
// Change your query where clause
query.where(cb.equal(countTagsSQ.getSelection(),Long.valueOf(tags.size())))
In this way, if Tags have a size of 10, it will return those Ads that have 10 of the tags you enter.
I develop my project web application and I wanted to change searching profiles feature to use JPA Criteria. My idea is to ommit these searching criteria, which fields in html form were left blank. The difficult part is to write Predicate with String List of interests.
public List<Profile> searchProfiles(String sex, String city, List<String> interests) {
List<String> emptyInterests = new ArrayList<>(); emptyInterests.add("");
Session session = this.sessionFactory.getCurrentSession();
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<Profile> criteriaQuery = builder.createQuery(Profile.class);
Root<Profile> root = criteriaQuery.from(Profile.class);
List<Predicate> predicates = new ArrayList<>();
if(!"".equals(sex)) {
predicates.add(builder.equal(root.get("sex"), sex ));
}
if(!"".equals(city)) {
predicates.add(builder.equal(root.get("city"), city ));
}
if(!emptyInterests.equals(interests)) {
// REASON OF ASKING THIS QUESTION
}
criteriaQuery.select(root).where(predicates.toArray(new Predicate[]{}));
return session.createQuery(criteriaQuery).list();
}
In the last "if" block I want to add Predicate which will means more or less "add Profile to results list if its String list of interests (Profile class field) contains all elements from method argument "interests" ". This condition in normal list filtering it would look like:
for(Profile profile : profiles) {
if(profile.getInterests().contains(interests))
results.add(profile);
}
Edit:
Following code causes ClassCastException: java.base/java.lang.String cannot be cast to java.base/java.util.List, in the return line.
if(!emptyInterests.equals(interests))
{
Expression<String> interestsExpression = root.get("interests");
Predicate interestsPredicate = interestsExpression.in(interests);
predicates.add(interestsPredicate);
}
I'm new here, I have a problem writting a method where I tried to call a expression with criteria using JPA and Spring, I have the next code:
#Override
public List<ContractOrder> getOrdersByIn(List<String> paramsIn ) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<ContractOrder> query = builder.createQuery(ContractOrder.class);
Root<ContractOrder> root = query.from(ContractOrder.class);
Expression<String> exp = root.get("**order_id**");
Predicate predicateIn = exp.**in**(paramsIn);
ParameterExpression<Long> pexp = builder.parameter(Long.class,"order_id");
Predicate predicateLike = builder.like(exp, pexp);
query.where(builder.or(predicateIn,predicateLike));
TypedQuery<ContractOrder> queryT = entityManager.createQuery(query.select(root));
queryT.setParameter(0, Long.valueOf("%5"));
List<ContractOrder> lista = queryT.getResultList();
return lista;
}
Where "order_id" is mapping as type long and I want to pass a parameter like this "%5".
Can you help me with it?
Regards!!
Why this %5 ???
It's a variable ???????
queryT.setParameter(0, Long.valueOf(5));
or
queryT.setParameter(0, Long.valueOf("5"));
should be OK.
Why ** and not just * ?I don't understand...
Predicate predicateIn = exp.**in**(paramsIn);
Is it compile?????
I have requirement in Java to fire a query on MS SQL like
select * from customer
where customer.name in ('abc', 'xyz', ...,'pqr');
But I have this IN clause values in the form of ArrayList of String. For ex: the list look like {"abc","xyz",...,"pqr"}
I created a Prepared Statement :
PreparedStatement pStmt = conn.prepareStatement(select * from customer
where customer.name in (?));
String list= StringUtils.join(namesList, ",");
pStmt.setString(1,list);
rs = pStmt.executeQuery();
But the list is like "abc,xyz,..,pqr", but I want it as "'abc','xyz',..,'pqr'"
so that I can pass it to Prepares Statement.
How to do it in JAva with out GUAVA helper libraries.
Thanks in Advance!!
I know this is a really old post but just in case someone is looking for how you could do this in a Java 8 way:
private String join(List<String> namesList) {
return String.join(",", namesList
.stream()
.map(name -> ("'" + name + "'"))
.collect(Collectors.toList()));
}
List<String> nameList = ...
String result = nameList.stream().collect(Collectors.joining("','", "'", "'"));
For converting the string you can try this:
String list= StringUtils.join(namesList, "','");
list = "'" + list + "'";
But i dont thing it's a good idea to pass one string for multiple params.
Even if you formatted the String as you wish, it won't work. You can't replace one placeholder in the PreparedStatement with multiple values.
You should build the PreparedStatement dynamically to have as many placeholders as there are elements in your input list.
I'd do something like this :
StringBuilder scmd = new StringBuilder ();
scmd.append ("select * from customer where customer.name in ( ");
for (int i = 0; i < namesList.size(); i++) {
if (i > 0)
scmd.append (',');
scmd.append ('?');
}
scmd.append (")");
PreparedStatement stmt = connection.prepareStatement(scmd.toString());
if (namesList.size() > 0) {
for (int i = 0; i < namesList.size(); i++) {
stmt.setString (i + 1, namesList.get(i));
}
}
rs = stmt.executeQuery();
You can use a simple separator for this type of activity. Essentially you want an object that evaluates to "" the first time around but changes after the first request to return a defined string.
public class SimpleSeparator<T> {
private final String sepString;
boolean first = true;
public SimpleSeparator(final String sep) {
this.sepString = sep;
}
public String sep() {
// Return empty string first and then the separator on every subsequent invocation.
if (first) {
first = false;
return "";
}
return sepString;
}
public static void main(String[] args) {
SimpleSeparator sep = new SimpleSeparator("','");
System.out.print("[");
for ( int i = 0; i < 10; i++ ) {
System.out.print(sep.sep()+i);
}
System.out.print("]");
}
}
I did it as following with stream. Almost the same, but a bit shorter.
nameList = List.of("aaa", "bbb", "ccc")
.stream()
.map(name -> "'" + name + "'")
.collect(Collectors.joining(","));
I guess the simplest way to do it is using expression language like that:
String[] strings = {"a", "b", "c"};
String result = ("" + Arrays.asList(strings)).replaceAll("(^.|.$)", "\'").replace(", ", "\',\'" );
I have a CriteriaBuilder where I am trying to get characters starting from 0 to 10. However I am not able to get the desired output.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Emp> cq = cb.createQuery(Emp.class);
Root<Emp> c = cq.from(Emp.class);
cb.substring(c.<String>get("projDesc"), 0, 10);
cq.orderBy(cb.desc(c.get("salary")));
Query query = em.createQuery(cq);
.....
What could be the reason for this?
From the javadoc
Create an expression for substring extraction. Extracts a substring of
given length starting at the specified position. First position is 1.
Try doing cb.substring(c.<String>get("projDesc"), 1, 10);
I think you're forgetting to select the Expression<E>
Try cq.select(cb.substring(c.<String>get("projDesc"), 1, 10))
It will return List<String> if you need to Return the Emp you can use the
cb.construct(Emp.class, e.get("prop1"), e.get("prop2"), cb.substring(c.<String>get("projDesc"), 1, 10)));
I also, faced same problem in which I have to substring first three char and fetch all accounts starting from 107 which is number. for that I have used CriteriaBuilder and substring method like below.
Predicate accountNumber = criteriaBuilder.equal(criteriaBuilder.substring(from.get("accountNumber").as(String.class), 0, 3), cwipAcc);
but unfortunately it is not working for CriteriaBuilder and substring. so I have used like query to resolve this issue by given code below.
Predicate accountNumber = criteriaBuilder.like(from.get("accountNumber").as(String.class), String.valueOf(cwipAcc) + "%");
here, I have just fetched all the records which is starting from 107 and so on.
Example:
public List<GLCharts> findAccountForCWIP(Long cwipAcc, Long glInfo) {
Map<String, Object> data = new HashMap();
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<GLCharts> criteriaQuery = criteriaBuilder.createQuery(GLCharts.class);
Root<GLCharts> from = criteriaQuery.from(GLCharts.class);
Predicate accountNumber = criteriaBuilder.like(from.get("accountNumber").as(String.class), String.valueOf(cwipAcc) + "%");
Predicate glCompanyInfo = criteriaBuilder.equal(from.join("gLCompanyInfo").get("id"), glInfo);
Predicate finalPredicate = criteriaBuilder.and(accountNumber, glCompanyInfo);
criteriaQuery.select(from).where(finalPredicate).orderBy(Stream.of(criteriaBuilder.asc(from.get("accountNumber"))).collect(Collectors.toList()));
List<GLCharts> glChartsList = entityManager.createQuery(criteriaQuery).getResultList();
return glChartsList;
}
I faced the problem because my requirement was to use a number into substr. Following is the sample code.
#Override
public List<SampleProfile> findNonSampleProfileBySequence(Long SampleNo) {
List<SampleProfile> profiles = new ArrayList<>();
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<SampleProfile> criteriaQuery = criteriaBuilder.createQuery(SampleProfile.class);
Root<SampleProfile> SampleProfileRoot = criteriaQuery.from(SampleProfile.class);
List<Predicate> predicates = new ArrayList<Predicate>();
if (SampleUtil.isValidLong(SampleNo)) {
String SampleStr = Long.toString(SampleNo);
if (StringUtils.isNotBlank(SampleStr) && SampleStr.length() > 5) {
String SampleSequence = SampleStr.substring(5);
predicates.add(criteriaBuilder.equal(criteriaBuilder.substring(SampleProfileRoot.get(SampleProfile_.id).as(String.class), 6), SampleSequence));
predicates.add(criteriaBuilder.equal(SampleProfileRoot.get(SampleProfile_.address).get(Address_.department), SampleStr.substring(0,3)));
}
}
if (!CollectionUtils.isEmpty(predicates)) {
criteriaQuery.where(criteriaBuilder.and(Iterables.toArray(predicates, Predicate.class)));
profiles = entityManager.createQuery(criteriaQuery).setMaxResults(AbstractJpaDAO.MAX_ROW_LIMIT).getResultList();
}
return profiles;
}
Also note that for performance benefits you have to create an index on the same.
The Cast Keyword is important as Hibernate Dialect will create query like this, thus, it has to match with your index.
CREATE INDEX MY_SCHEMA_OWNER.IDX_SUBSTR_SMP_CODE ON MY_SCHEMA_OWNER.SMP_PROFILE (SUBSTR(**CAST**(SMP_CODE AS VARCHAR2(255 CHAR)),6));