JPQL select based on non null dto values - java

Hi everyone I am using spring JPA and I am trying to filter my query result based on nonnull DTO field values.
The reason is every time I have different DTO fields and I should get books based on just the existing fields or in other words the nonnull fields.
My DB table is books and I have the following DTO for it
public class BookDTO {
private String name;
private String title;
private String isbn;
private String author;
private int pages;
private String size;
}
I searched over the web but I didn't find a solution for this kind of issue anyway to achieve this using Spring JPQL

You can do this using JpaSpecificationExecutor (scroll down to section 5
This way you can programmatically define what fields are you going to add to your where clause, like this .:
(Specification<Book>) (book, cq, cb) ->
cb.and(
// You can dynamically construct that array of predicates based on which fields are set in the form
cb.like(book.get("author"), "%" + author + "%"),
cb.like(book.get("title"), "%" + title + "%")
)

one alternative is to use Spring Data JPA Specification.
With that you can create your criteria search for the fields that are present.
public Predicate toPredicate
(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
if (criteria.getOperation().equalsIgnoreCase(">")) {
return builder.greaterThanOrEqualTo(
root.<String> get(criteria.getKey()), criteria.getValue().toString());
}
else if (criteria.getOperation().equalsIgnoreCase("<")) {
return builder.lessThanOrEqualTo(
root.<String> get(criteria.getKey()), criteria.getValue().toString());
}
else if (criteria.getOperation().equalsIgnoreCase(":")) {
if (root.get(criteria.getKey()).getJavaType() == String.class) {
return builder.like(
root.<String>get(criteria.getKey()), "%" + criteria.getValue() + "%");
} else {
return builder.equal(root.get(criteria.getKey()), criteria.getValue());
}
}
return null;
}
reference: https://www.baeldung.com/rest-api-search-language-spring-data-specifications

You can try Example query.
https://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html/ch17.html#querycriteria-examples
Example queries
The class org.hibernate.criterion.Example allows you to construct a query criterion from a given instance.
Cat cat = new Cat();
cat.setSex('F');
cat.setColor(Color.BLACK);
List results = session.createCriteria(Cat.class)
.add( Example.create(cat) )
.list();
Version properties, identifiers and associations are ignored. By default, null valued properties are excluded.
You can adjust how the Example is applied.
Example example = Example.create(cat)
.excludeZeroes() //exclude zero valued properties
.excludeProperty("color") //exclude the property named "color"
.ignoreCase() //perform case insensitive string comparisons
.enableLike(); //use like for string comparisons
List results = session.createCriteria(Cat.class)
.add(example)
.list();
You can even use examples to place criteria upon associated objects.
List results = session.createCriteria(Cat.class)
.add( Example.create(cat) )
.createCriteria("mate")
.add( Example.create( cat.getMate() ) )
.list();

Related

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.

Generate a “where in” statement using the Criteria API in Hibernate

I put in place the following specification that represents the predicate construction for querying Students based on their age and their ClassRoom's teachers' name (one student can have one or more classroom)
public class StudentSpecification implements Specification<Student> {
private final Integer age;
public StudentSpecification(Integer age){
this.age = age;
}
#Override
public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.<Integert>get(age), Integer.valueOf(v)));
SetJoin<Student, ClassRoom> classRooms = root.join(Student_.classRooms);
predicates.add(criteriaBuilder.equal(classRooms.get(ClassRoom_.teacher), "Marta"));
predicates.add(criteriaBuilder.equal(classRooms.get(ClassRoom_.teacher), "Fowler"));
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
}
}
Here is an example of data :
Student
_____________________________________________
ID CLASSROOM_ID NAME AGE
2 120 Pascal 22
8 120 Bryan 21
ClassRoom
_____________________________________________
ID CLASSROOM_ID TEACHER
1 120 Marta
2 120 McAllister
2 120 Fowler
The specification returns nothing.
When I see the generated statement, I understand why it doesn't work :
where
classRooms.teacher=?
and classRooms.teacher=?
I was expecting something like :
where
students0.classroom_id in (
select classrooms0.classroom_id where
classRooms.teacher=?
)
and students0.classroom_id in (
select classrooms0.classroom_id where
classRooms.teacher=?
)
Question : how can make a query with the Criteria API work in my case ?
You will need Subquery to achieve what you want if you need to stick with Criteria API. Otherwise, HQL can be a better choice for the sake of readability compared to the verbosity of Criteria API.
The idea is to generate individual queries and make a manual join through a predicate. So no need for a Join or SetJoin.
First, note that there are some mistakes in your code. The most obvious one is the path you used to reach the age field. You should use the generated metamodel instead of hard coded strings.
predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get(Student_.age), age));
instead of :
predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.<Integert>get(age), Integer.valueOf(v)));
Then, here is the complete solution :
public static Specification<Student> withTeacherAndName(){
return new Specification<Student>() {
#Override
public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> criteriaQuery,
CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get(Student_.age), 20));
Subquery<String> sq1 = criteriaQuery.subquery(String.class);
Root<Classroom> classroomRoot = sq1.from(Classroom.class);
sq1.select(classroomRoot.get(Classroom_.classroomId));
sq1.where(criteriaBuilder.equal(classroomRoot.get(Classroom_.teacher), "Marta"));
Subquery<String> sq2 = criteriaQuery.subquery(String.class);
Root<Classroom> classroomRoot2 = sq2.from(Classroom.class);
sq2.select(classroomRoot2.get(Classroom_.classroomId));
sq2.where(criteriaBuilder.equal(classroomRoot2.get(Classroom_.teacher), "Fowler"));
criteriaQuery.where(criteriaBuilder.equal(root.get(Student_.classroomId), sq1));
criteriaQuery.where(criteriaBuilder.equal(root.get(Student_.classroomId), sq2));
return criteriaBuilder.and(predicates.toArray(new Predicate[]{}));
}
};
}
So basically you are creating a subquery for each criteria.
The code needs a refactoring (a loop for example).
If you want an in Clause instead of an equals clause just use it:
predicates.add(criteriaBuilder.in(classRooms.get(ClassRoom_.teacher), "Marta"));
predicates.add(criteriaBuilder.in(classRooms.get(ClassRoom_.teacher), "Fowler"));
See https://docs.oracle.com/javaee/6/api/javax/persistence/criteria/CriteriaBuilder.html#in(javax.persistence.criteria.Expression)

Hibernate criteria.list() returns duplicates

I know this kind of question has been asked thousands of times already, but I think I have a different problem. So, I have a class A, which has a collection of objects of class B and a getter for this collection:
public class A {
private String Id;
private ArrayList<B> bees;
public ArrayList<B> getBees(){
ArrayList<Criterion> criterions = new ArrayList<>();
criterions.add(Restrictions.eq("classAId", this.Id));
return (ArrayList<B>)DatabaseHelper.getObjectByCriteria(criterions, B.class);
}
}
And DatabaseHelper.getObjectByCriteria code:
public static List getObjectByCriteria(ArrayList<Criterion> cr, Class classType){
List result = new ArrayList();
Session session = MyHibernateUtil.getSessionFactory().openSession();
Criteria criteria = session.createCriteria(classType);
if (cr != null){
for (Criterion criterion : cr){
criteria.add(criterion);
}
}
result = criteria.list();
if (session.isOpen()){
session.close();
}
return result;
}
When I run this query (select * from table_b where id = 123) in MySQLWorkbench I get two different rows with different values.
However, the "result" list returned by DatabaseHelper.getObjectByCriteria contains two indentical objects with identical ids and values.
I don't use any kind of hibernate annotations
I have show_sql property set to "true", and I checked the real query being run by hibernate and its ok

JPA Query.getResultList() - use in a generic way

I'm creating a complex query with multiple tables and need to list the result. Usually, I'm using the EntityManager and map the result to the JPA-Representation:
UserEntity user = em.find(UserEntity.class, "5");
Then I can access all values as the user UserEntity class defines it. But how can I access the field-values returned from a native, multiple-table query? What I get is a List of Objects. That's fine so far, but what "is" that Object? Array? Map? Collection? ...
//simpleExample
Query query = em.createNativeQuery("SELECT u.name,s.something FROM user u, someTable s WHERE s.user_id = u.id");
List list = query.getResultList();
//do sth. with the list, for example access "something" for every result row.
I guess the answer is quite simple, but most examples out there just show the usage when directly casting to a targetClass.
PS: In the example I could use the class-mappings of course. But in my case someTable is not managed by JPA, and therefore I don't have the entity nor do I have a class-representation of it, and since I'm joining like 20 tables, I don't want to create all the classes just to access the values.
General rule is the following:
If select contains single expression and it's an entity, then result is that entity
If select contains single expression and it's a primitive, then result is that primitive
If select contains multiple expressions, then result is Object[] containing the corresponding primitives/entities
So, in your case list is a List<Object[]>.
Since JPA 2.0 a TypedQuery can be used:
TypedQuery<SimpleEntity> q =
em.createQuery("select t from SimpleEntity t", SimpleEntity.class);
List<SimpleEntity> listOfSimpleEntities = q.getResultList();
for (SimpleEntity entity : listOfSimpleEntities) {
// do something useful with entity;
}
If you need a more convenient way to access the results, it's possible to transform the result of an arbitrarily complex SQL query to a Java class with minimal hassle:
Query query = em.createNativeQuery("select 42 as age, 'Bob' as name from dual",
MyTest.class);
MyTest myTest = (MyTest) query.getResultList().get(0);
assertEquals("Bob", myTest.name);
The class needs to be declared an #Entity, which means you must ensure it has an unique #Id.
#Entity
class MyTest {
#Id String name;
int age;
}
The above query returns the list of Object[]. So if you want to get the u.name and s.something from the list then you need to iterate and cast that values for the corresponding classes.
I had the same problem and a simple solution that I found was:
List<Object[]> results = query.getResultList();
for (Object[] result: results) {
SomeClass something = (SomeClass)result[1];
something.doSomething;
}
I know this is defenitly not the most elegant solution nor is it best practice but it works, at least for me.
Here is the sample on what worked for me. I think that put method is needed in entity class to map sql columns to java class attributes.
//simpleExample
Query query = em.createNativeQuery(
"SELECT u.name,s.something FROM user u, someTable s WHERE s.user_id = u.id",
NameSomething.class);
List list = (List<NameSomething.class>) query.getResultList();
Entity class:
#Entity
public class NameSomething {
#Id
private String name;
private String something;
// getters/setters
/**
* Generic put method to map JPA native Query to this object.
*
* #param column
* #param value
*/
public void put(Object column, Object value) {
if (((String) column).equals("name")) {
setName(String) value);
} else if (((String) column).equals("something")) {
setSomething((String) value);
}
}
}
What if you create a bean with all required properties and cast the result using Java 8+ streams?
Like this:
public class Something {
private String name;
private String something;
// getters and setters
}
And then:
import javax.persistence.Query;
...
Query query = em.createNativeQuery("SELECT u.name,s.something FROM user u, someTable s WHERE s.user_id = u.id", Something.class);
List<?> list = query.getResultList();
return list
.stream()
.map(item -> item instanceof Something ? (Something) item : null)
.collect(Collectors.toList());
That way, you don't need to return List<Object[]> nor hide the warning with #SuppressWarnings("unchecked")
Ps.:
1 - I know that this post is very old. But... I'm here in 2021, so others will be coming here too =)
2 - This is wrong or bad practice? Let me know :D
You can also update your hibernate to a version greater than 5.4.30.final

Categories