Add specification from a loop - jpa

In a spring boot 3 application I try to use specification
public Page<User> advancedSearch(UserSearch search, Pageable page) {
String[] splittedValues = search.name.split(" ");
Specification<User> hasPersonWithName = (Root<User> root, CriteriaQuery<?> cq, CriteriaBuilder cb) -> {
...
return pre;
};
return findAll(specification, page);
}
I need for every value in splittedValues to add a global specification
for (String splittedName: splittedValues) {
specification.or(splittedName);
}
and pass it to findAll
I don't understand how to do it
Edit
you solution seem to work but
that generate
where
1=1
or like e1_0.name "%bob%"
or like e1_0.name "%jame%"
It's there a way to get
where
1=1
and (
like e1_0.name "%bob%"
or like e1_0.name "%jame%"
)

Using or operator , you combine multiple conditions, checking whether the name of User contains one of the splittedValues. Predicate is then returned from specification.
I hope that was what you wanted, in case I didn't understand, please correct me. Here is an example if so, adapt to your own code.
Edit: To get 1=1 and (like e1_0.name "%bob%" or like e1_0.name "%jame%"), you can use cb.or method to combine the conditions, resulting nameConditions is then combined with pre using cb.and.
public Page<User> advancedSearch(UserSearch search, Pageable page) {
String[] splittedValues = search.name.split(" ");
Specification<User> hasPersonWithName = (Root<User> root, CriteriaQuery<?> cq, CriteriaBuilder cb) -> {
Predicate pre = cb.conjunction();
Predicate nameConditions = null;
for (String splittedName: splittedValues) {
if (nameConditions == null) {
nameConditions = cb.like(root.get("name"), "%" + splittedName + "%");
} else {
nameConditions = cb.or(nameConditions, cb.like(root.get("name"), "%" + splittedName + "%"));
}
}
if (nameConditions != null) {
pre = cb.and(pre, nameConditions);
}
return pre;
};
return findAll(hasPersonWithName, page);
}

Related

How to write Criteria Query Builder for select all from List?

I need a query which will return only results which are having all values from the list.
Something like:
SELECT *
FROM ads_tags
WHERE tag_value("a", "b", "c");
I know that query is not good, but the point is that I want to filter ads but only ads which are having all tags from the list.
With my code, I am getting all ads that have at least one tag from the list. That is because I am using IN interface.
#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<List<String>> in = builder.in(tagsJoin.get("name"));
if (tags != null && tags.size() > 0) {
// in.value(tags);
tags.forEach(tag - > in.value(tags));
/*
* 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);
}
}
What I need to write instead of In interface to get all ads which have ALL tags from the list, not at least one?
You need a join for each element of type Tag in your list.
It's hard to fix your code because it's not a minimal example, however the following should help:
CriteriaQuery<Ads> query = builder.createQuery(Ads.class);
Root<Ads> ads = query.from(Ads.class);
List<Predicate> predicates = new ArrayList<>();
for (String tag : tags) {
Join<Ads, Tag> tagsJoin = ads.join("adsTags");
predicates.add(builder.equal(tagsJoin.get("name"), tag));
}

return a boolean - jdbcTemplate

I would like to return a boolean value using in this method:
public Boolean isSizeOk(String transactionId){
String sqlQuery = "SELECT true FROM customer_pool WHERE id = "+ transactionID + " AND level = 13)";
//The next line is the problem.
//If I am returning a Boolean List, I can write
List <Boolean> sizeResult = jdbcTemplate.queryForList(sqlQuery, Boolean.class, transactionId);
//But since I only want a boolean value, what will the statement be?
Boolean sizeResult = jdbcTemplate......?
return sizeResult;
}
Kindly help. Thanks.
If you want to write a method that checks that a record exists in the database you can use the following code:
Integer cnt = jdbcTemplate.queryForObject(
"SELECT count(*) FROM customer_pool WHERE id = ? AND level = 13)", Integer.class, id);
return cnt != null && cnt > 0
Counting rows in SQL just in order to get simple information about non-emptiness of result may be unnecessary overkill, you want just ask result set for first row and finish. For simple queries by primary key or other index the performance might be similar, however, for complex queries, or full table scan queries it might be slow. In Spring I prefer simple utility method
public boolean exists(String sql, Object... args) {
boolean result = query(sql, args, new ResultSetExtractor<Boolean>() {
#Override
public Boolean extractData(ResultSet rs) throws SQLException,DataAccessException {
boolean result = rs.next();
return result;
}
});
return result;
}
(Google "sql exists vs count" for more info.)
What about
// Change query accordingly
String query = "SELECT 1 FROM " + tableName + " WHERE " + idColumnName + " = ? LIMIT 1";
try {
jdbcTemplate.queryForObject(query, new Object[]{id}, Long.class);
return true;
} catch (EmptyResultDataAccessException e) {
return false;
}
Case 1: In case you are returning boolean:
Just check the size of sizeResult List, if the size is greater than 0 return true else return false.
Case 2: If you are returning boolean list then return type must be a boolean List.You must
write the method as:
public List<Boolean> isSizeOk(String transactionId, int sizeLimit){
String sqlQuery = "SELECT true FROM customer_pool WHERE id = ? AND level = 13)";
List <Boolean> sizeResult = jdbcTemplate.queryForList(sqlQuery, Boolean.class, transactionId);
Boolean sizeResult = jdbcTemplate......?
return sizeResult;
}

Why am I getting an InvalidCastException with competing Newtonshoft.Json.Linq.[JArray,JObject] with very similar code/data?

This code works fine - returns the single record that matches the REST query:
Popul8TheGrid("http://localhost:28642/api/subdepartments/1/10");
private void Popul8TheGrid(string URIToPass)
{
try
{
dataGridView1.DataSource = GetRESTData(URIToPass);
}
catch (WebException webex)
{
MessageBox.Show("Eek, a mousey-pooh! ({0})", webex.Message);
}
}
private JArray GetRESTData(string uri)
{
var webRequest = (HttpWebRequest) WebRequest.Create(uri);
var webResponse = (HttpWebResponse) webRequest.GetResponse();
var reader = new StreamReader(webResponse.GetResponseStream());
string s = reader.ReadToEnd();
return JsonConvert.DeserializeObject<JArray>(s);
}
However, this code, which also should return a single record:
private const string BASE_URI = "http://localhost:28642/api/";
. . .
string URIToPass = string.Format("{0}deliveryitems/{1}", BASE_URI, numericUpDownDeliveryItemId.Value);
Popul8TheGrid(URIToPass);
...fails, with "InvalidCastException was unhandled ... Message=Unable to cast object of type 'Newtonsoft.Json.Linq.JObject' to type 'Newtonsoft.Json.Linq.JArray'".
Why might that be? The data returned from the first (working) snippet comes from an MS Access "database"
The data from the second (failing) snippet is from test data:
public DeliveryItemRepository()
{
// Just some bogus/test data for now
Add(new DeliveryItem
{
Id = 1, InvoiceNumber = "123", UPC_PLU = "456", VendorItemId = "789", PackSize = 1, Description = "Something", Quantity = 5, Cost = 1.25M,
Margin = 0.25M, ListPrice = 1.50M, DepartmentNumber = 42, Subdepartment = "5"
});
. . .
This is the Controller method; it works fine when entering the URI in a browser.
// Enter "http://localhost:28642/api/1"
[Route("api/DeliveryItems/{ID:int}")]
public DeliveryItem GetDeliveryItemById(int ID)
{
return _deliveryItemRepository.GetById(ID);
}
...but why that would matter, I know not...
UPDATE
Interestingly enough (perhaps I'm easily amused), this, OTOH, works:
MessageBox.Show(GetRESTScalarVal("http://localhost:28642/api/deliveries/1"));
. . .
private string GetRESTScalarVal(string uri)
{
var client = new WebClient();
return client.DownloadString(uri);
}
By "works," I mean it returns this:
So DownloadString() will even return an entire json "record" and my use of the word "Scalar" was misleading. Maybe I should have said "Single" instead, although that can be confusing, too, what with the data type of the same appellation.
The question still remains as to how I can populate a datagrid with a single json "record"
UPDATE 2
Oddly enough, if I use a different Controller method to get the one record, it works:
private void GetDeliveryItemById()
{
//string uri = string.Format("deliveryitems/{0}", numericUpDownId.Value);
string uri = string.Format("deliveryitems/{0}/1", numericUpDownId.Value);
Popul8TheGrid(uri);
}
The commented out code is what blows up, whereas the other, with a provided const val of 1, works...kludgy, but it works.
UPDATE 3
Perhaps a clue/related to why it won't work when fetching one, but works otherwise, is this Repository code:
public SiteMapping GetById(int ID)
{
return siteMappings.Find(p => p.Id == ID);
}
public IEnumerable<SiteMapping> GetRange(int ID, int CountToFetch)
{
return siteMappings.Where(i => i.Id >= ID).Take(CountToFetch);
}
If GetById() is called with an ID that exists, it works; if one is passed that doesn't exist, though, it fails with, "InvalidOperationException was unhandled by user code . . . Message=Sequence contains no matching element"
Calling GetRange() works robustly - if passed a bogus pair of vals (no records), it simply shrugs its shoulders, rather than getting the old bulgy eye and screaming maniacally.
Changing it to so (see Simon Whitehead's answere here) works:
public SiteMapping GetById(int ID)
{
var entity = siteMappings.Find(p => p.Id == ID);
return entity == null ? null : entity;
}
So trying to find by a particular ID is fragile; trying to find by ID + Count works just fine. Why, I (still) don't know...
This may be somewhat kludgy, but it works:
private JArray GetRESTData(string uri)
{
try
{
var webRequest = (HttpWebRequest)WebRequest.Create(uri);
var webResponse = (HttpWebResponse)webRequest.GetResponse();
var reader = new StreamReader(webResponse.GetResponseStream());
string s = reader.ReadToEnd();
return JsonConvert.DeserializeObject<JArray>(s);
}
catch // This method crashes if only one json "record" is found - try this:
{
try
{
MessageBox.Show(GetScalarVal(uri));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
return null;
}
private string GetScalarVal(string uri)
{
var client = new WebClient();
return client.DownloadString(uri);
}

Count JPA and Invalid Path

After poking around Stack Overflow I found the following solution for counting problem. My requirement is to get the total number of matching rows, and return the first ten for pagination purposes.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<T> cq = cb.createQuery(clazz);
CriteriaQuery<Long> counterCq = cb.createQuery(Long.class);
counterCq.select(cb.count(counterCq.from(clazz)));
Predicate predicate= null;
Predicate predicate1 = null;
Root<T> root = cq.from(clazz);
for (Map.Entry<String, String> e : filters.entrySet()){
predicate = cb.and(cb.like(root.<String>get(e.getKey()), e.getValue()+ "%"));
}
if(predicate != null){
cq.where(predicate);
counterCq.where(predicate);
}
int pn = ( em.createQuery(counterCq).getSingleResult()).intValue();
logger.debug("number of pages is {}", pn);
setRowCount(pn);
if(sortField !=null && !sortField.trim().equals("")){
if(sortOrder == SortOrder.DESCENDING){
cq.orderBy(cb.desc(root.get(sortField)));
} else{
cq.orderBy(cb.asc(root.get(sortField)));
}
}
Query q = em.createQuery(cq);
q.setFirstResult(first);
q.setMaxResults(first+ps);
List<T> cats= (List<T>)q.getResultList();
This snippet makes hibernate to through
java.lang.IllegalArgumentException: org.hibernate.hql.ast.QuerySyntaxException: Invalid path: 'generatedAlias1.title' [select count(generatedAlias0) from Media as generatedAlias0 where generatedAlias1.title like :param0]
It seems like cq.from(clazz) cannot be applied for the other query.
Now my question: Is there a way to use the same predicate in both queries?
Your predicate list isn't assembled correctly. You have to 'and' predicates together into a single expression. I also prefer to build my predicate before performing the select for better readability.
Here's a refactor of your code to achieve the correct results:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<T> cq = cb.createQuery(clazz);
Root<T> root = cq.from(clazz);
// build predicate list - conjuction starts us with an empty 'and' predicate
Predicate predicate = cb.conjunction();
for (Map.Entry<String, String> e : filters.entrySet()) {
predicate = cb.and(predicate, cb.like(root.get(e.getKey()), e.getValue() + "%"));
}
// query total count
CriteriaQuery<Long> counterCq = cb.createQuery(Long.class);
counterCq.select(cb.count(root)).where(predicate);
int pn = (em.createQuery(counterCq).getSingleResult()).intValue();
logger.debug("number of pages is {}", pn);
setRowCount(pn);
// query results
cq.select(root).where(predicate);
if(sortField !=null && !sortField.trim().equals("")) {
if(sortOrder == SortOrder.DESCENDING) {
cq.orderBy(cb.desc(root.get(sortField)));
}
else {
cq.orderBy(cb.asc(root.get(sortField)));
}
}
TypedQuery<T> q = em.createQuery(cq);
q.setFirstResult(first);
q.setMaxResults(first+ps);
List<T> list = q.getResultList();

how could i use JPA criteria query api for joined columns?

i am new to JPA and i have a problem with it.
suppose that we have two tables which are related
by a ManytoOne association, which means that
table A stores a primary key of table B within it.
when these two tables are mapped to JPA entities
i have a problem for search on this situation.
i have used an existing code from richfaces demo, to handle filtering and sorting by using
JPA. this code is using input parameters to create criteria query.
this is the code:
private CriteriaQuery<T> createSelectCriteriaQuery() {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(entityClass);
Root<T> root = criteriaQuery.from(entityClass);
if (arrangeableState != null) {
List<Order> orders = createOrders(criteriaBuilder, root);
if (!orders.isEmpty()) {
criteriaQuery.orderBy(orders);
}
Expression<Boolean> filterCriteria = createFilterCriteria(criteriaBuilder, root);
if (filterCriteria != null) {
criteriaQuery.where(filterCriteria);
}
}
return criteriaQuery;
}
protected Expression<Boolean> createFilterCriteriaForField(String propertyName, Object filterValue, Root<T> root, CriteriaBuilder criteriaBuilder) {
String stringFilterValue = (String) filterValue;
if (Strings.isNullOrEmpty(stringFilterValue)) {
return null;
}
stringFilterValue = stringFilterValue.toLowerCase(arrangeableState.getLocale());
Path<String> expression = root.get(propertyName);
Expression<Integer> locator = criteriaBuilder.locate(criteriaBuilder.lower(expression), stringFilterValue, 1);
return criteriaBuilder.gt(locator, 0);
}
private Expression<Boolean> createFilterCriteria(CriteriaBuilder criteriaBuilder, Root<T> root) {
Expression<Boolean> filterCriteria = null;
List<FilterField> filterFields = arrangeableState.getFilterFields();
if (filterFields != null && !filterFields.isEmpty()) {
FacesContext facesContext = FacesContext.getCurrentInstance();
for (FilterField filterField : filterFields) {
String propertyName = (String) filterField.getFilterExpression().getValue(facesContext.getELContext());
Object filterValue = filterField.getFilterValue();
Expression<Boolean> predicate = createFilterCriteriaForField(propertyName, filterValue, root, criteriaBuilder);
if (predicate == null) {
continue;
}
if (filterCriteria == null) {
filterCriteria = predicate.as(Boolean.class);
} else {
filterCriteria = criteriaBuilder.and(filterCriteria, predicate.as(Boolean.class));
}
}
}
return filterCriteria;
}
the code is okay, when i try to filter columns(not joined columns), but when i try to
query on joined column, the produced query is not correct and it throws exception.
so my question is that, how could i use JPA criteria query api, to filter rows by both
joined columns and non-joined coulmns.
thanks
I don't believe you can treat join columns like regular ones.
for example if you want to filter on id of B, you would have to create a join from A to B , then use B_.id to match values.
Shay