How do I get a specific EntityManager? - java

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

Related

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"));

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?

Spring Data JPA with PageRequest and Predicate failing

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!

Get random object from SQL database via Hibernate

I have following MySql dependent code ( ORDER BY RAND() ) . I would like to know if there is hibernate HQL alternative for it (admin is boolean tag indicating that the user as an admin). This is working code:
public long getRandomAdmin() {
Session session = getSession();
Query selectQuery = session.createSQLQuery("SELECT user_id FROM users WHERE admin = '1' ORDER BY RAND()");
selectQuery.setMaxResults(1);
List<BigInteger> list = null;
try {
list = selectQuery.list();
} catch (HibernateException e) {
log.error(e);
throw SessionFactoryUtils.convertHibernateAccessException(e);
}
if (list.size() != 1) {
log.debug("getRandomAdmin didn't find any user");
return 0;
}
log.debug("found: " + list.get(0));
return list.get(0).longValue();
}
See this link:
http://www.shredzone.de/cilla/page/53/how-to-fetch-a-random-entry-with-hibernate.html
Criterion restriction = yourRestrictions;
Object result = null; // will later contain a random entity
Criteria crit = session.createCriteria(Picture.class);
crit.add(restriction);
crit.setProjection(Projections.rowCount());
int count = ((Number) crit.uniqueResult()).intValue();
if (0 != count) {
int index = new Random().nextInt(count);
crit = session.createCriteria(Picture.class);
crit.add(restriction);
result = crit.setFirstResult(index).setMaxResults(1).uniqueResult();
}
This is what you want. Keep Hibernate as an abstraction layer while still being able to query a random object. Performance suffers a bit, though.
Although I've been using Hibernate a lot, I don't know a more elegant way that is easy to use. Imho you should wrap that method behind a facade.
Since the Criterion used in the accepted answer is deprecated, I figured out how to do it with the CriteriaBuilder & CriteriaQuery and just wanted to share it here. I used the pattern described here to extend my repository by the custom method:
#Repository
public class UserRepositoryCustomImpl implements UserRepositoryCustom {
#Autowired
EntityManager em;
public User findRandomUserInCountry(String countryCode) throws NotFoundException {
CriteriaBuilder qb = em.getCriteriaBuilder();
CriteriaQuery<Long> cqCount = qb.createQuery(Long.class);
Root<User> userCountRoot = cqCount.from(User.class);
cqCount.select(qb.count(userCountRoot)).where(qb.equal(userCountRoot.get("countryCode"), countryCode));
int count = em.createQuery(cqCount).getSingleResult().intValue();
System.out.println("Count of users: " + count);
if (0 != count) {
int index = new Random().nextInt(count);
CriteriaQuery<User> cqUser = qb.createQuery(User.class);
Root<User> userRoot = cqUser.from(User.class);
cqUser.select(userRoot).where(qb.equal(userRoot.get("countryCode"), countryCode));
User randomUser = em.createQuery(cqUser).setFirstResult(index).setMaxResults(1)
.getSingleResult();
System.out.println("Random user: " + randomUser.getName());
return randomUser;
} else {
throw new NotFoundException("No users available in repository for country: " + countryCode);
}
}
}

Categories