I'm trying to use Specification interface. So far so good but now I would like to use an optional search criteria.
In the code below if customerId(Long) is null I would like to get all the customers from db and if not only the the customer with the specific Id. Now only works the second part.
Is it possible to get all the customers if the given Id is null?
Thanks in advance!
Specification<Order> customer = (root, query, cb) -> {return cb.equal(root.get("customerId"), customerId);
};
So you need to create a condition to return an empty (default) CriteriaBuilder on null customerId:
Specification<Order> customer = (root, query, cb) -> {
if(customerId == null){
return cb; // return empty CriteriaBuilder
}
return cb.equal(root.get("customerId"), customerId);
};
Related
I have a repo with own Specification implementation with toPredicate method as main query construction and I try to add order by expression:
public Predicate toPredicate(#NotNull Root<Strategy> root,
#NotNull CriteriaQuery<?> query,
#NotNull CriteriaBuilder builder) {
Predicate predicate = builder.conjunction();
List<Expression<Boolean>> exps = predicate.getExpressions();
... adding different expressions to exps.add(...)
// I get an id for descending sort due to Postgres just increment it.
Order orderBy = builder.desc(root.get("id"));
Expression<?> expression = orderBy.getExpression();
// Problem here.
exps.add(expression);
return predicate;
}
Expression from orderBy.getExpression() is <?> generic but original expressions list expect <Boolean> type. How to connect them?
Specification is only intended for encoding where-clauses. If you want to order your result use a Sort instance as an additional parameter.
Sorting with pagination means sorting should be field of Pageable like this:
Pageable pagination = PageRequest.of(pageNumber, pageSize, Sort.by("id").descending());
I am using JpaRepository Pageable query for pagination. All the things are working fine except the sort field case sensitive issue. Following query is used for getting list.
Pageable pageable = null;
if (paginationRequestDTO.getSortOrder().equalsIgnoreCase("desc"))
pageable = new PageRequest(page, size, Sort.Direction.DESC, sortfiled);
else
pageable = new PageRequest(page, size, Sort.Direction.ASC, sortfiled);
Page<Audi> audiPage = null;
audiencePage = audiRepository.search(paginationRequestDTO.getSearchKey(), pageable);
Audi table values are: apple,az,Ajay,Bala.
when i search with sortorder of asc and sort field name,
original output : Ajay,Bala,apple,az.
Expected output: Ajay,apple,az,Bala.
I am using mysql database. table engine - Innodb,characterst-utf8,collate-utf8_bin.
Please note that its not duplicate question.i didn't get exact answer for this question.thanks in advance.
Edited: as harsh pointed out correctly, this needs to be solved on database level, using correct collation. This is important, because you probably want to have an index on the sort column for best performance.
But there are other use cases, which could combine filtering together with sorting by something other, than a pure column value, e.g. by length of description, sum or average of a column, etc. For that reason, I am including a JPA solution:
I struggled with this recently and I am afraid, that the Pageable interface does not support this out of box.
The solution was to use EntityManager, CriteriaBuilder, CriteriaQuery, Specification and implement the paging manually. You can find the solution here.
You need to construct the Page object manually:
public Page<Audi> getPage(int pageNumber, int pageSize, String descriptionFilter, Sorting sorting) {
return new PageImpl<>(
getPageItems(pageNumber, pageSize, descriptionFilter, sorting),
PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.ASC, sorting.name())),
getTotalCount(descriptionFilter)
);
}
getPageItems selects the page using LIMIT and OFFSET
private List<Audi> getPageItems(int pageNumber, int pageSize, String descriptionFilter, Sorting sorting) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Audi> query = cb.createQuery(Audi.class);
Root<Audi> root = query.from(Audi.class);
query.where(createSpecification(descriptionFilter).toPredicate(root, query, cb));
if (sorting.equals(Sorting.descriptionCaseInsensitive)) {
query.orderBy(cb.asc(cb.lower(root.get("description"))));
} else {
throw new UnsupportedOperationException("Unsupported sorting: " + sorting.name());
}
query.select(root);
return em.createQuery(query)
.setFirstResult(pageNumber * pageSize)
.setMaxResults(pageSize)
.getResultList();
}
getTotalCount selects count(distinct(*)),
private long getTotalCount(String descriptionFilter) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Long> query = cb.createQuery(Long.class);
Root<Audi> root = query.from(Audi.class);
query.where(createSpecification(descriptionFilter).toPredicate(root, query, cb));
query.select(cb.countDistinct(root));
// getSingleResult can return null, if no rows fulfill the predicate
return Optional.ofNullable(em.createQuery(query).getSingleResult()).orElse(0L);
}
Both reuse the same predicate, which filters rows:
private Specification<Audi> createSpecification(String descriptionFilter) {
return Specification.where(
(root, query, criteriaBuilder) ->
criteriaBuilder.like(criteriaBuilder.lower(root.get("description")), "%" + descriptionFilter.toLowerCase() + "%")
);
}
The objective is to get certain entities with specific ids. I want to create a vararg method that will take entity ids and return the entity list according to the ids:
#Override
public List<Entity> getEntities(long... ids) {
Session s = sessionFactory.getCurrentSession();
s.beginTransaction();
Criteria criteria = s.createCriteria(Entity.class);
for (long id : ids) {
// for very id I want to create a Restriction
// but Restriction goes like
// criteria.add(Restrictions.or(Restrictions.eq("id", id),Restrictions.eq("id", id)));
}
s.getTransaction().commit();
return null;
}
Usage will be:
List<Entity> list = getEntities(453,282,781,784);
How to create such criteria query?
A variable number of OR id = ? clause is like an IN clause. This is done using the Restrictions.in method:
criteria.add(Restrictions.in("id", ids));
criteria.add(Restrictions.in("id", ids));
criteria.addOrder(Order.asc("id"));
will solve your problem it will return your result in the same order which you passed into varargs
I'm using Eclipselink and have a tricky problem regarding JPA NamedQueries.
My database table contains a column which is from type VARCHAR and stores a comma separated list of keywords as one String.
How can I create a NamedQuery in JPA to search theese keywords?
I'd like to give a list of Strings as a parameter and as a result I'd like to have a list of objects which keyword list contain one of the Strings from the parameter list.
Maybe like the following:
List<String> keywordList = new ArrayList<String>();
keywordList.add("test");
keywordList.add("car");
List<Object> result = em.createNamedQuery("findObjectByKeywords", Object.class)
.setParameter("keywords", keywordList)
.getResultList();
Unfortunately I'm not such a big database/SQL expert. Maybe someone of you can help me?
I hope you understand my problem.
Edit:
I am developing on Weblogic 10.3.6, which means I am not able to use JPA 2.0 features.
Edit2:
I managed to activate JPA 2.0 in my Weblogic Server with the help of Oracle Enterprise Pack for Eclipse. Problem solved, I think.
VALID FOR JPA2.0
As Bhesh commented a simple JPQL won't make it. The resulting SQL has to contain a where clause similar to following:
where keywords like '%keyword1%' or keywords like '%keyword2%' or ... or keywords like '%keywordN%'
This means: We need a loop here!
You could try to build a JPQL by yourself like Bhesh suggested in his first comment, though as he also stated it is not a brilliant idea. But don't worry - JPA provides also a Criteria API which comes handy in such situations. So, although you're not going to have a named query, you can still make it with JPA this way:
public List<YourEntity> findAllByKeywords(List<String> keywords){
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<YourEntity> query = builder.createQuery(YourEntity.class);
Root<YourEntity> root = query.from(YourEntity.class);
List<Predicate> predicates = new LinkedList<>();
for (String keyword : keywords) {
predicates.add(builder.like(root.<String>get("keywords"), "%" + keyword + "%"));
}
return entityManager.createQuery(
query.select(root).where(
builder.or(
predicates.toArray(new Predicate[predicates.size()])
)
))
.getResultList();
}
or (always slightly better with Guava)
public List<YourEntity> findAllByKeywords(List<String> keywords){
final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<YourEntity> query = builder.createQuery(YourEntity.class);
final Root<YourEntity> root = query.from(YourEntity.class);
return entityManager.createQuery(
query.select(root).where(
builder.or(
transform(keywords, toPredicateFunction(builder, root)).toArray(new Predicate[]{})
)
))
.getResultList();
}
private Function<String, Predicate> toPredicateFunction(final CriteriaBuilder builder, final Root<YourEntity> root) {
return new Function<String, Predicate>() {
#Override
public Predicate apply(String input) {
return builder.like(root.<String>get("keywords"), "%" + input + "%");
}
};
}
We have to query data from database where we need to find entities matching a list of key value pairs. We thought it would be a nice idea to use Spring Data JPA as we need also pagination.
The tables we created are like below:
terminal(ID,NUMBER,NAME);
terminal_properties(ID,KEY,VALUE,TERMINAL_FK);
Is it possible to define a query method to fetch all terminals with properties containing given key/value pairs ?
Something like this: List<Terminal> findByPropertiesKeyAndValue(List<Property>);
I didn't execute the code, but given the correct import statements, this at least compiles. Depending on your entity definition, some properties may need to be adapted and in any case, you should get an idea of how to approach this.
My criteria query is based on the following SQL:
SELECT * FROM TERMINAL
WHERE ID IN (
SELECT TERMINAL_FK FROM TERMINAL_PROPERTIES
WHERE (KEY = 'key1' AND VALUE = 'value1')
OR (KEY = 'key2' AND VALUE = 'value2')
...
GROUP BY TERMINAL_FK
HAVING COUNT(*) = 42
)
Where you list each name/value pair and 42 simply represents the number of name/value pairs.
So I assume you defined a repository like this:
public interface TerminalRepository extends CrudRepository<Terminal, Long>, JpaSpecificationExecutor {
}
It's important to extend JpaSpecificationExecutor in order to make use of the criteria API.
Then you can build a criteria query like this:
public class TerminalService {
private static Specification<Terminal> hasProperties(final Map<String, String> properties) {
return new Specification<Terminal>() {
#Override
public Predicate toPredicate(Root<Terminal> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
// SELECT TERMINAL_FK FROM TERMINAL_PROPERTIES
Subquery<TerminalProperty> subQuery = query.subquery(TerminalProperty.class);
Root propertyRoot = subQuery.from(TerminalProperty.class);
subQuery.select(propertyRoot.get("terminal.id"));
Predicate whereClause = null;
for (Map.Entry<String, String> entry : properties.entrySet()) {
// (KEY = 'key1' AND VALUE = 'value1')
Predicate predicate = builder.and(builder.equal(propertyRoot.get("key"),
entry.getKey()), builder.equal(propertyRoot.get("value"), entry.getValue()));
if (whereClause == null) {
whereClause = predicate;
} else {
// (...) OR (...)
whereClause = builder.or(whereClause, predicate);
}
}
subQuery.where(whereClause);
// GROUP BY TERMINAL_FK
subQuery.groupBy(propertyRoot.get("terminal.id"));
// HAVING COUNT(*) = 42
subQuery.having(builder.equal(builder.count(propertyRoot), properties.size()));
// WHERE ID IN (...)
return query.where(builder.in(root.get("id")).value(subQuery)).getRestriction();
}
};
}
#Autowired
private TerminalRepository terminalRepository;
public Iterable<Terminal> findTerminalsWith(Map<String, String> properties) {
// this works only because our repository implements JpaSpecificationExecutor
return terminalRepository.findAll(hasProperties(properties));
}
}
You can obviously replace Map<String, String> with Iterable<TerminalProperty>, although that would feel odd because they seem to be bound to a specific Terminal.