Spring Data JPA with PageRequest and Predicate failing - java

Im using Spring data Jpa specification and I would like to obtain non repetead registries, so I use distinct in query like that.
public static Specification<Actividad> criteriosConsultaGruposHandling(final String cif, final String razonSocial, final String nombreComercial, final List<Long> idsHandling) {
Specification<Actividad> spec = new Specification<Actividad>() {
#Override
public Predicate toPredicate(Root<Actividad> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
query.distinct(true);
List<Predicate> predicates = new ArrayList<Predicate>();
if(cif != null && !cif.equals("")){
predicates.add(cb.like(cb.upper(root.join(Actividad_.empresa).get(Empresa_.cif)), "%" + cif.toUpperCase() + "%" ));
}
if(razonSocial != null && !razonSocial.equals("")){
predicates.add(cb.like(cb.upper(root.join(Actividad_.empresa).get(Empresa_.razonsocial)), "%" + razonSocial.toUpperCase() + "%" ));
}
if(nombreComercial != null && !nombreComercial.equals("")){
predicates.add(cb.like(cb.upper(root.join(Actividad_.empresa).get(Empresa_.nombrecomercial)), "%" + nombreComercial.toUpperCase() + "%" ));
}
if(idsHandling != null && !idsHandling.isEmpty()){
Predicate pred = cb.conjunction();
pred.getExpressions().add(root.join(Actividad_.grupohandlingactividads).get(Grupohandlingactividad_.grupohandling).get(Grupohandling_.idsubgrupo).in(idsHandling));
predicates.add(pred);
}
return cb.and(predicates.toArray(new Predicate[0]));
}
};
return spec;
}
}
but when I do the findAll with the specification and pagerequest the page resulting from query is bad
Specification<Actividad> specificationActividad = ActividadSpecifications.criteriosConsultaGruposHandling(actividadForm.getEmpresa().getCif(),
actividadForm.getEmpresa().getRazonsocial(), actividadForm.getEmpresa().getNombrecomercial(),listaGruposAsignados);
PageRequest pageRequest = new PageRequest(actividadForm.getPagina() -1, actividadForm.getMaxRegistros(), actividadForm.getOrder());
Page<Actividad> resultados = actividadRepository.findAll(specificationActividad, pageRequest);
When I call findAll without pageRequest parameter I obtain rows without repetitions but when I call it with it donĀ“t elimininate the repetitive rows.
Anyone could help me? Thanks in advance!

Related

How do I get a specific EntityManager?

I have an entity with ~70 variables. And I want to make some custom queries for searching for filtering, using different combinations of them, without having to create 10 000 find methods and then applying some logic to pick the correct one to use.
#PersistenceContext
EntityManager entityManager;
private CriteriaBuilder criteriaBuilder;
public PageImpl<TClaimModel> findAllWithFilters(TClaimModelSearchCriteria filters){
if (criteriaBuilder == null) criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<TClaimModel> criteriaQuery = criteriaBuilder.createQuery(TClaimModel.class);
Root<TClaimModel> claimRoot = criteriaQuery.from(TClaimModel.class);
Predicate predicate = getPredicate(filters, claimRoot);
criteriaQuery.where(predicate);
if(filters.getSortDirection().equals(Sort.Direction.ASC))
criteriaQuery.orderBy(criteriaBuilder.asc(claimRoot.get(filters.getSort())));
else
criteriaQuery.orderBy(criteriaBuilder.desc(claimRoot.get(filters.getSort())));
TypedQuery<TClaimModel> typedQuery = entityManager.createQuery(criteriaQuery);
typedQuery.setFirstResult(filters.getPage() * filters.getPageSize());
typedQuery.setMaxResults(filters.getPageSize());
long claimsCount = getClaimsCount(predicate);
return new PageImpl<>(typedQuery.getResultList(), filters.getPageable(), claimsCount);
}
private Predicate getPredicate(TClaimModelSearchCriteria filters, Root<TClaimModel> claimRoot) {
List<Predicate> predicates = new ArrayList<>();
if (Objects.nonNull(filters.getClaimNum()))
predicates.add(criteriaBuilder.like(claimRoot.get("claimNum"), "%" + filters.getClaimNum() + "%"));
if (Objects.nonNull(filters.getClaimDate()))
predicates.add(criteriaBuilder.equal(claimRoot.get("claimDate"), filters.getClaimDate()));
if (Objects.nonNull(filters.getStartSearchDate())) {
if (Objects.nonNull(filters.getEndSearchDate()))
predicates.add(criteriaBuilder.between(claimRoot.get("claimDate"), filters.getStartSearchDate(), filters.getEndSearchDate()));
else
predicates.add(criteriaBuilder.greaterThanOrEqualTo(claimRoot.get("claimDate"), filters.getStartSearchDate()));
} else if (Objects.nonNull(filters.getEndSearchDate()))
predicates.add(criteriaBuilder.lessThanOrEqualTo(claimRoot.get("claimDate"), filters.getEndSearchDate()));
// repeat for each possible filter;
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
}
private long getClaimsCount(Predicate predicate) {
CriteriaQuery<Long> countQuery = criteriaBuilder.createQuery(Long.class);
Root<TClaimModel> countRoot = countQuery.from(TClaimModel.class);
countQuery.select(criteriaBuilder.count(countRoot)).where(predicate);
return entityManager.createQuery(countQuery).getSingleResult();
}
This is my filtering code so far, but I get an error "Not an entity: class com.activiti.entities.alfresco.TClaimModel". After reading about this error, it turns out that I can't use #PersistenceContext to get the EntityManager.
So then, HOW do I get the EntityManager? I can autowire an EntityManagerFactory, but it doesn't have the createQuery method.
EDIT: The project is using 2 databases. Using #Autowire on the EntityManager also doesn't work, as I get:
Field entityManager in com.activiti.repository.alfresco.TClaimCriteriaRepository required a single bean, but 2 were found:
- org.springframework.orm.jpa.SharedEntityManagerCreator#0: defined by method 'createSharedEntityManager' in null
- org.springframework.orm.jpa.SharedEntityManagerCreator#1: defined by method 'createSharedEntityManager' in null
As a workaround, I manually built the request...
#Autowired
protected DataSourceService dataSourceService;
public Map<String, Object> findAllWithFilters(TClaimModelSearchCriteria filters){
Map<String, Object> response = new HashMap<>();
try {
List<DataSource> dataSources = dataSourceService.getDataSourcesForTenant(SecurityUtils.getCurrentUserObject().getTenantId());
CommonJdbcTemplate commonJdbcTemplate = new CommonJdbcTemplate();
if (dataSources.size() > 0) {
JdbcTemplate jdbcTemplate = commonJdbcTemplate.getJdbcTemplate(dataSources.get(0));
String sql = buildRequestSql(filters);
List<Map<String, Object>> data = jdbcTemplate.queryForList(sql);
String counter = buildCountRequestSql(filters);
Long count = (Long) jdbcTemplate.queryForList(counter).get(0).get("count");
response.put("total", count);
response.put("data", data);
return response;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private String buildRequestSql(TClaimModelSearchCriteria filters) {
StringBuilder sql = new StringBuilder();
sql.append("select * from t_claim");
getWhere(sql, filters);
sql.append(" order by " + filters.getSort());
if (filters.getSortDirection() == Sort.Direction.DESC)
sql.append(" desc");
if (filters.getPage() != 0) {
sql.append(" offset " + filters.getPage() * filters.getPageSize());
}
sql.append(" fetch first " + filters.getPageSize() + " row only");
return sql.toString();
}
private String buildCountRequestSql(TClaimModelSearchCriteria filters) {
StringBuilder sql = new StringBuilder();
sql.append("select count(*) from t_claim");
getWhere(sql, filters);
return sql.toString();
}
...
But I'd still like to know the proper way to do it :P

Create Search Specification with Dates range

I want to create a Search specification where I can select data based on Date range. I tried this:
#Getter
#Setter
public class BillingSummarySearchParams {
private LocalDateTime startDate;
private LocalDateTime endDate;
}
Search Specification
public List<BillingSummaryFullDTO> findBillingInvoicesSummary(BillingSummarySearchParams params)
{
Specification<BillingSummary> spec = (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
if (params.getStartDate() != null | params.getEndDate() != null) {
predicates.add(cb.like(cb.lower(root.get("startDate")), "%" + params.getStartDate() + "%"));
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
};
return billingSummaryService.findAll(spec).stream().map(billingSummaryMapper::toFullDTO).collect(Collectors.toList());
}
Search SQL
public List<BillingSummary> findAll(Specification<BillingSummary> spec)
{
String hql = "select e from " + BillingSummary.class.getName() + " e where e.createdAt BETWEEN :startDate AND :endDate ORDER BY e.createdAt DESC";
TypedQuery<BillingSummary> query = entityManager.createQuery(hql, BillingSummary.class).setParameter("startDate", spec).setParameter("endDate", spec);
List<BillingSummary> list = query.getResultList();
return list;
}
It's not clear to me how I can build the specification with 2 dates to work and with only one.
What is the proper way to get the dates from the spec object?
I think you are missunderstanding what Specifications are used for. They aren't meant to be used in combination with "old" typed queries. If you set a Specification as parameter for them it should result in errors because the jpa provider has no idea how to work with that object.
Instead they are implemented to have an easier time to create and use criteria queries in spring data. The first step to make them work would be to implement JpaSpecificationExecutor in your repository. The interface contains methods like findAll(Specification<T> spec) and spring will automatically create these methods for your repository. Spring generates the criteria query in the background and adds the predicate you create in your specification to it.
#Repository
public interface BillingSummaryRepository extends JpaRepository<BillingSummary, Long>, JpaSpecificationExecutor<BillingSummary> {
//Other methods
}
Specification<BillingSummary> spec = (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
if (params.getStartDate() != null | params.getEndDate() != null) {
predicates.add(cb.like(cb.lower(root.get("startDate")), "%" + params.getStartDate() + "%"));
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
};
List<BillingSummary> summaries = billingSummaryService.findAll(spec, Sort.by(Sort.Direction.DESC, "createdAt"));

How to handle pagination in JPA (Criteria and Predicates)

I'm fetching the results from DB using criteria and predicates and I got my result list , and I'm trying to apply pagination and sorting but it's not working. Please help me where I'm missing, Here is my code:
private Page<Books> getFiltereBooks(Params params,
PageRequest sortOrder) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Books> criteria = builder.createQuery(Books.class);
Root<Books> booksRoot = criteria.from(Books.class);
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(builder.equal(booksRoot.get("id"), params.getRequestId()));
predicates.add(builder.like(builder.lower(booksRoot.get("name")),
"%" + params.getName().toLowerCase() + "%"));
criteria.where(builder.and(predicates.toArray( new Predicate[predicates.size()])));
criteria.orderBy(builder.desc(booksRoot.get("id")));
List<Books> result = em.createQuery(criteria).getResultList();
int total = result.size();
Page<Books> result1 = new PageImpl<>(result, sortOrder, total);
return result1;
}
when I use this code :
Page<Books> result1 = new PageImpl<>(result, sortOrder, total);
it's not working, I want to return a Page Object. any help is appreciated.
You can try this
private Page<Books> getFiltereBooks(Params params,
Pageable pageable) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Books> criteria = builder.createQuery(Books.class);
Root<Books> booksRoot = criteria.from(Books.class);
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(builder.equal(booksRoot.get("id"), params.getRequestId()));
predicates.add(builder.like(builder.lower(booksRoot.get("name")),
"%" + params.getName().toLowerCase() + "%"));
criteria.where(builder.and(predicates.toArray( new Predicate[predicates.size()])));
criteria.orderBy(builder.desc(booksRoot.get("id")));
// This query fetches the Books as per the Page Limit
List<Books> result = em.createQuery(criteria).setFirstResult((int) pageable.getOffset()).setMaxResults(pageable.getPageSize()).getResultList();
// Create Count Query
CriteriaQuery<Long> countQuery = builder.createQuery(Long.class);
Root<Books> booksRootCount = countQuery.from(Books.class);
countQuery.select(builder.count(booksRootCount)).where(builder.and(predicates.toArray(new Predicate[predicates.size()])));
// Fetches the count of all Books as per given criteria
Long count = em.createQuery(countQuery).getSingleResult();
Page<Books> result1 = new PageImpl<>(result, pageable, count);
return result1;
}
Predicate[] getPredicate(Root<Entity> root, CriteriaBuilder criteriaBuilder, filter params) {
Predicate[] predicatesArr = null;
List<Predicate> predicates = new ArrayList<>();
// fill your predicates list here
predicatesArr = predicates.toArray(new Predicate[predicates.size()]);
return predicatesArr;
}
You need to add a method for creating predicates for each query, Note here you are using the same predicates list "which drived from different query" for both queries [pagination and count]
Root<Books> booksRoot = criteria.from(Books.class);
predicates.add(builder.equal(booksRoot.get("id"), params.getRequestId()));

Jpa Criteria API count

I've been trying to get total rows count and to do so, I've used JPA Criteria API but it throws an error at Long count = em.createQuery(sc).getSingleResult(); line and saying java.lang.IllegalStateException: No criteria query roots were specified . I've done some research but couldn't narrow the problem.
Here's my code snippet;
#PersistenceContext
public EntityManager em;
......
public Page<UserDTO> findByCriteria(String filters, Pageable pageable) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<UserDTO> cq = cb.createQuery(UserDTO.class);
Root<UserDTO> iRoot = cq.from(UserDTO.class);
List<Predicate> predicates = new ArrayList<Predicate>();
if (StringUtils.isNotEmpty(filters)) {
predicates.add(cb.like(cb.lower(iRoot.<String>get("login")), "%" + filters.toLowerCase() + "%"));
}
Predicate[] predArray = new Predicate[predicates.size()];
predicates.toArray(predArray);
CriteriaQuery<Long> sc = cb.createQuery(Long.class);
sc.select(cb.count(iRoot));
sc.where(predArray);
Long count = em.createQuery(sc).getSingleResult();
cq.where(predArray);
List<Order> orders = new ArrayList<Order>(2);
orders.add(cb.asc(iRoot.get("name")));
orders.add(cb.asc(iRoot.get("desc")));
cq.orderBy(orders);
TypedQuery<UserDTO> query = em.createQuery(cq);
Page<UserDTO> result = new PageImpl<UserDTO>(query.getResultList(), pageable, count);
return result;
}
EDITED AND WORKING CODE;
public Page<UserDTO> findByCriteria(String columnName, String filters, Pageable pageable) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<UserDTO> cq = cb.createQuery(UserDTO.class);
Root<UserDTO> iRoot = cq.from(UserDTO.class);
List<Predicate> predicates = new ArrayList<Predicate>();
if (StringUtils.isNotEmpty(filters)) {
predicates.add(cb.like(cb.lower(iRoot.<String>get(columnName)), "%" + filters.toLowerCase() + "%"));
}
Predicate[] predArray = new Predicate[predicates.size()];
predicates.toArray(predArray);
Long count = calculateCount(filters);
cq.where(predArray);
List<Order> orders = new ArrayList<Order>(2);
orders.add(cb.asc(iRoot.get("firstName")));
orders.add(cb.asc(iRoot.get("lastName")));
cq.orderBy(orders);
TypedQuery<UserDTO> query = em.createQuery(cq);
Page<UserDTO> result = new PageImpl<UserDTO>(query.getResultList(), pageable, count);
return result;
}
public Long calculateCount(String filters) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Long> sc = cb.createQuery(Long.class);
Root<UserDTO> iRoot = sc.from(UserDTO.class);
List<Predicate> predicates = new ArrayList<Predicate>();
if (StringUtils.isNotEmpty(filters)) {
predicates.add(cb.like(cb.lower(iRoot.<String>get("login")), "%" + filters.toLowerCase() + "%"));
}
Predicate[] predArray = new Predicate[predicates.size()];
predicates.toArray(predArray);
sc.select(cb.count(iRoot));
sc.where(predArray);
Long count = em.createQuery(sc).getSingleResult();
return count;
}
As anticipated in the comment, you need to explicitly add CriteriaQuery#from() method:
sc.from(UserDTO.class);
The from method is often not used because the API provider defaults to the entity class specified in the CriteriaQuery constructor. Not in this case however, because the class is a Long and it doesn't correspond to an entity class.
See also:
Oracle's JAVA EE Tutorial

How to get selected fields from table using hibernate (I am not using Criteria object but CriteriaQuery object so setProjection solution won't work)

I want to get selected fields from table with filters on the top of it.
For example :
If I want to filter my results on the basis of an attribute in the table named "created_on", I would do something like http://example.com/users/?created_on = 01-12-2016
I am achieving above task by doing this :
public List<User> searchUser(List<SearchCriteria> params
, int offset, int limit) {
Session session = mysqlConnectionFactory.getSession();
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<User> query = builder.createQuery(User.class);
Root r = query.from(User.class);
Predicate predicate = builder.conjunction();
if (params != null) {
for (SearchCriteria param : params) {
if (param.getOperation().equalsIgnoreCase(">")) {
predicate = builder.and(predicate,
builder.greaterThanOrEqualTo(r.get(param.getKey()),
param.getValue().toString()));
} else if (param.getOperation().equalsIgnoreCase("<")) {
predicate = builder.and(predicate,
builder.lessThanOrEqualTo(r.get(param.getKey()),
param.getValue().toString()));
} else if (param.getOperation().equalsIgnoreCase(":")) {
if (r.get(param.getKey()).getJavaType() == String.class) {
predicate = builder.and(predicate,
builder.like(r.get(param.getKey()),
"%" + param.getValue() + "%"));
} else {
predicate = builder.and(predicate,
builder.equal(r.get(param.getKey()), param.getValue()));
}
}
}
query.where(predicate);
}
List<User> Users = session.createQuery(query)
.setFirstResult(offset).setMaxResults(limit).getResultList();
return Users;
}
Now I also wish to incorporate the functionality which will return selected fields for me, how can I do that?

Categories