How to Implement sum in CriteriaQuery - java

I want to fetch the data with the sum of one column and group by two fields from the database where the parameters will be dynamic
I tried to implement using predicates I am able to get the group by but sum is not working.
Page<PaymentDetail> aggregatedPaymentDetails2 = paymentDetailRepository.findAll(new Specification<PaymentDetail>() {
#Override
public Predicate toPredicate(Root<PaymentDetail> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
final List<Predicate> predicates = new ArrayList<>();
query.multiselect(root.get("id"), root.get("lockVersion"), cb.sum(root.get("amountPaid")), root.get("referenceNumber"), root.get("paymentSlot"));
query.groupBy(root.get("referenceNumber"), root.get("paymentSlot").get("id"));
for (final QueryCriterion queryCriterion : queryCriterionList) {
final OperatorEnum operatorEnum = queryCriterion.getOperatorEnum();
From join = root;
final String[] attributes = queryCriterion.getKey().split("\\.");
for (int i = 0, attributesLength = attributes.length - 1; i < attributesLength; i++) {
join = join.join(attributes[i], JoinType.LEFT);
}
final Path path = join.get(attributes[attributes.length - 1]);
final Object value = dataTypesHelper.typeCastValue(path, queryCriterion);
predicates.add(operatorEnum.getOperator().getPredicateByKeyAndValue(path, value, cb));
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
},pageable);
I expect the output to be page with sum of amountpaid and group by paymentslotid, PaymentSlot has a onetomany relationship with paymentdetails

Specifications are intended to produce a Predicate which is essentially a where clause.
That you also can trigger side effects through the JPA API is an unfortunate weakness of the JPA API. There is no support in Spring Data JPA to change the return value through a Specification. Use a custom method implementation for this.

Related

get fields in join path with criteria

I wanna know is there a way to do something like this in hibernate using criteriaBuilder
select users.first_name,orders.payable,order_item.product_title
from "order" orders
join users on orders.user_id_fk = users.id_pk
join order_item on orders.id_pk = order_id_fk
I need this specially if I have to use group by. I search and google and also read this article but have no clue how can I do this in hibernate:
Query Selection Other Than Entities
querycriteria
hibernate-facts-multi-level-fetching
also I code this for selecting field in first layer and it worked perfectly for selecting first layer but it need some change to work with join and select field from other table than root:
<R> List<R> reportEntityGroupBy(List<String> groupBy, List<String> selects, Class<T> root, Class<R> output) {
CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
CriteriaQuery<R> criteriaQuery = criteriaBuilder.createQuery(output);
Root<T> rootQuery = criteriaQuery.from(root);
if (selects == null || selects.isEmpty()) {
selects = groupBy;
}
criteriaQuery.multiselect(selects.stream().map(rootQuery::get).collect(Collectors.toList()));
criteriaQuery.groupBy(groupBy.stream().map(rootQuery::get).collect(Collectors.toList()));
I use Hibernate 5.4.22.Final and use entityGraph for my join.
I don't know how your selects look like, but I suppose you are using paths i.e. attributes separated by .. In that case, you have to split the paths and call Path#get for each attribute name.
List<Path> paths = selects.stream()
.map(select -> {
Path<?> path = rootQuery;
for (String part : select.split("\\.")) {
path = path.get(part);
}
return path;
})
.collect(Collectors.toList()));
criteriaQuery.multiselect(paths);
criteriaQuery.groupBy(paths);

JPA CriteriaBuilder: ListJoin with IN query on joined cloumn

I have a use-case where I need to search a table on a list of values. Below is the schema:
CASE table
Case_Id
CASE_CIN table
Case_Id
cin
CASE & CASE_CIN table are joined via Many to Many.
I need to search cases based on provided list of CINs. This is the SQL that I'm trying to implement:
select distinct c.* from case c left join case_cin cc on c.case_id = cc.case_id where cc.cin in ("cin1", "cin2", "cin3");
This is how I designed my filter criteria based on just a single CIN:
public static Specification<CaseEntity> buildSpecification(CaseSearchRequestDto criteria, String userName) {
return (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
if (!isEmpty(criteria.getAssociatePartyCins())) {
predicates.add(buildAssociatePartyCinsFilterPredicate(root, cb, query, criteria.getAssociatePartyCins());
}
return cb.and(predicates.toArray(new Predicate[0]));
};
}
private static Predicate buildAssociatePartyCinsFilterPredicate(Root<CaseEntity> root, CriteriaBuilder cb, CriteriaQuery query, List<String> cins) {
Predicate filterPredicate = cb.disjunction();
ListJoin<CaseEntity, String> cinsJoin = root.joinList(FIELD_CASE_CINS, LEFT);
filterPredicate.getExpressions().add(startWithString(cinsJoin, cb, **cins.get(0)**); // need to change logic here.
query.distinct(true);
return filterPredicate;
}
I'd also like to have the exact match for every CIN rather that startWithString.
Can anyone help modify the code to allow search by multiple values?
It turns out to be way simpler than I thought. Just need to replace the condition with cinsJoin.in(cins).
private static Predicate buildAssociatePartyCinsFilterPredicate(Root<CaseEntity> root, CriteriaBuilder cb, CriteriaQuery query, List<String> cins) {
Predicate filterPredicate = cb.disjunction();
ListJoin<CaseEntity, String> cinsJoin = root.joinList(FIELD_CASE_CINS, LEFT);
filterPredicate.getExpressions().add(cinsJoin.in(cins));
query.distinct(true);
return filterPredicate;
}

Dynamic search term SQL query with Spring JPA or QueryDSL

I am trying to learn QueryDSL in order to return the results of a single search term from Postgres:
#GetMapping("/product/contains/{matchingWords}")
public List<ProductModel> findByTitleContaining(#PathVariable String matchingWords) {
QProductModel product = QProductModel.productModel;
JPAQuery<?> query = new JPAQuery<>(em);
List<ProductModel> products = query.select(product)
.from(product)
.where(product.title.toLowerCase()
.contains(matchingWords.toLowerCase()))
.fetch();
return products;
}
But I also want to search for any number of search terms, for example, say this is my list of search terms divided by the plus symbol:
String[] params = matchingWords.split("[+]");
How can I dynamically create contains(params[0]) AND/OR contains(params[1] AND/OR ... contains(params[n]) using either QueryDSL or any Java/Spring query framework? I see QueryDSL has a predicate system, but I still don't understand how to dynamically create a query based on a variable number of parameters searching in the same column.
I figured it out. It's a little non-intuitive, but using BooleanBuilder and JPAQuery<?> you can create a dynamic series of boolean predicates, which return a list of matches.
Example:
QProductModel product = QProductModel.productModel;
JPAQuery<?> query = new JPAQuery<>(//entity manager goes here//);
// example list of search parameters separated by +
String[] params = matchingWords.split("[+]");
BooleanBuilder builder = new BooleanBuilder();
for(String param : params) {
builder.or(product.title.containsIgnoreCase(param));
}
// then you can put together the query like so:
List<ProductModel> products = query.select(product)
.from(product)
.where(builder)
.fetch();
return products;

JPA Hibernate Order by alphanumeric field

I am using Jpa/Hibernate to access MariaDB in a Spring Boot application
I am strugling to sort data by an alphanumeric field containng numbers that might end with one letter (pattern \d+[a-z]?)
e.g.
10
104
20a
100b
and I need them ordered like this
10
20a
100b
104
I a bulding my own query with the Criteria Api because I also have to do some complex filtering.
#Transactional(readOnly = true)
public class EntryRepositoryImpl implements EntryRepositoryCustom {
#PersistenceContext
private EntityManager entityManager;
#Override
public Page<Entry> get(MultiValueMap<String, String> parameters, Pageable pageable) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
// count total number of filterd entries
Long totalResultCount = getResultCount(parameters, criteriaBuilder);
// build query to get filterd entries
CriteriaQuery<Entry> selectQuery = criteriaBuilder.createQuery(Entry.class);
Root<Entry> getRoot = selectQuery.from(Entry.class);
CriteriaQuery<Entry> select = selectQuery.select(getRoot);
addFilters(parameters, criteriaBuilder, getRoot, select);
// add sorting
List<javax.persistence.criteria.Order> sortOrders = JpaUtils.translateSorting(pageable,
getRoot);
select.orderBy(sortOrders);
// get one page of filterd entries
List<Entry> results = getPageResults(pageable, select);
return new PageImpl<>(results, pageable, totalResultCount);
}
/**
* Translate spring to jpa sorting.
*/
public static List<javax.persistence.criteria.Order> translateSorting(Pageable pageable,
Root<Entry> root) {
List<Sort.Order> orders = new ArrayList<>();
if (pageable.getSort() != null) {
pageable.getSort().iterator().forEachRemaining(orders::add);
}
return orders.stream().
map(order -> {
String[] parts = order.getProperty().split("\\.");
String field = parts[0];
Path path = parts.length == 2 ? root.join(field).get(parts[1]) : root.get(field);
return new OrderImpl(path, order.isAscending());
})
.collect(Collectors.toList());
}
I already have a custom comparator but it seems, there is no way to translate it so the DB could use it.
So far I found the following solutions/ideas
using #SortComparator, but it is not feasible for my use case because the ordering has to happen in the database, because there are over 500k complex rows.
this sql base solution but don't know how to translate it into the Criteria Api.
after looking at the function of CriteriaBuilder (javadoc) I got the idea to split the value into the numeric and string parts and apply to orders but there is not function to split with a regular expression.
Edit:
For now I did split the field into 2 and use two sort expression.

eclipselink jpa criteria reference unmapped column

when writing jpql queries I can reference not mapped columns with COLUMN() (I know it is eclipselink specific).
Is there any way I can reference unmapped column when building criteria using javax.persistence.criteria.CriteriaBuilder, javax.persistence.criteria.Predicate, etc?
The problem I am facing: I have postgresql table with full text search column. I do not want it to be mapped to entity objects but I qould like to use is in queries (and I need also queries using CriteriaBuilder).
Edit: code example
I have a table: X (id integer, name varchar, fts tsvector)
"fts" column is full text search index data.
I need entity class without "fts" attribute, like:
#Entity class X {
#Id Integer id;
#Column String name;
}
so that fts data is never fetched (for performance), but I need to be able to query that column. I can do this with jpql:
SELECT t FROM X t WHERE COLUMN('fts', t) IS NOT NULL;
but how can I reference such column when building criteria like:
Specification<Fts> spec = new Specification<Fts>() {
#Override
public Predicate toPredicate(Root<Fts> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
return builder.notNull(root.<String>get("fts"));
}
};
other choice:
How to add attribute in entity class that reference table column but is never fetched? I tried adding #Basic(fetchType = FetchType.LAZY) but it does not work and value is fetched upon query...
Ok. I managed to solve it this way:
Specification<Fts> spec = new Specification<Fts>() {
#Override
public Predicate toPredicate(Root<Fts> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
JpaCriteriaBuilder cb = (JpaCriteriaBuilder) builder;
List<String> args = new ArrayList();
args.add("FTS query");
javax.persistence.criteria.Expression<Boolean> expr = cb.fromExpression(
cb.toExpression(
cb.function("", Boolean.class, cb.fromExpression(((RootImpl) root).getCurrentNode().getField("fts")))
)
.sql("? ## to_tsquery(?)", args)
);
// getField() allows to reference column not mapped to Entity
query.where(expr);
return null; // or return cb.isTrue(expr); and then not need to set query.where()
}
};

Categories