Query creation in Spring Data - dynamic where clause - java

Is there a way in Spring data to dynamically form the where clause?
What I want to do is have a method (which is like the findBy / get method) which runs a WHERE and AND using the mentioned properties which are NOT NULL.
For example,
Consider the object Person [firstName, lastName, age, gender]
Our method looks something like this
findBy_IfNotNullFirstName_AndIfNotNullLastName_AndIfNotNullAge_AndIfNotNullGender(String firstName, String lastName, Integer age, String gender)
Thanks.

A simpler option is to test if the parameter is null right in the JPQL query:
Exemple from my project:
#Query("select m from MessageEntity m " +
"join fetch m.demandeAnalyseEntities d " +
"where (:patientId is null or d.noPtn= :patientId) " +
" and " +
" ( :labNbr is null or d.noLab= :labNbr) " +
" and " +
" ( :reqDate is null or d.dteReq= :reqDate) " +
" and " +
" ( :reqNum is null or d.noReq= :reqNum) "
)
List<MessageEntity> findMessagesWithDemandesOnly(#Param("patientId") Long pid,
#Param("labNbr") Integer labNo,
#Param("reqDate") String reqDate,
#Param("reqNum") Integer reqNum,
Pageable pageable);

Take a look at JPA Specification and Predicate, and Even better QueryDSL, there both supported by spring data repositories.
This article provide an example:
http://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/

Another solution: You can extend your JPA repo interface using custom fragment interfaces.
Define your custom methods on a new interface
public interface PersonFragRepository {
List<User> findPersonByWhatever(
String firstName, String lastName, String age, String gender);
}
Provide the implementation
public class PersonFragRepositoryImpl implements PersonFragRepository {
#PersistenceContext
private EntityManager entityManager;
#Override
List<User> findPersonByWhatever(
String firstName, String lastName, String age, String gender) {
...
}
}
Extends your JPA interface
public interface PersonRepository
extends JpaRepository<Person, Integer>, PersonFragRepository

Related

Map Query result to dto

I have these 3 independent table i.e Student, Teacher and Subject. Independent here refers that there is no relation in these tables.
I want the count of all these tables . SQL query looks like -
SELECT
(SELECT COUNT(*) FROM Student as ST,
(SELECT COUNT(*) FROM Teacher as TE,
(SELECT COUNT(*) FROM Subject as SU
Now I want to map this result into dto .
The DTO looks like
public class CountDto{
Integer student;
Integer teacher;
Integer subject;
}
The repository call looks like -
#Query(value = "SELECT\r\n"
+ " (SELECT COUNT(*) FROM Student) as ST, \r\n"
+ " (SELECT COUNT(*) FROM Teacher) as TE,\r\n"
+ " (SELECT COUNT(*) FROM Subject) as SU", nativeQuery = true)
public CountDto getCount();
While calling this function I get following error stating
"message": "Failed to convert from type [java.lang.Object[]] to type [com.rbl.mdm.dto.CountDto ] for value '{16, 16 , 34}'; nested exception is org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.Integer] to type [com.rbl.mdm.dto.CountDto]"
How should I convert my response to desired DTO ?
You can declare CountDto as a public interface and it should work. It's called a Projection in terms of Spring. Or you can use SqlResultSetMapping or ConstructorResult along with your class.
you don't have to implement it by any entity class, just create it like an independent interface even within the repository file:
public interface StudentRepository extends CrudRepository<Student, Long> {
#Query(value = "SELECT\r\n"
+ " (SELECT COUNT(*) FROM Student) as ST, \r\n"
+ " (SELECT COUNT(*) FROM Teacher) as TE,\r\n"
+ " (SELECT COUNT(*) FROM Subject) as SU", nativeQuery = true)
Counts getCount();
public static interface Counts {
Integer getST();
Integer getTE();
Integer getSU();
}
}
So here is the answer to do -
The DTO will look like
public CountDto{
private Integer studentTotal;
private Integer teacherTota;
private Integer subjectTotal;
}
The repository call -
#Query(value = "SELECT\r\n"
+ " (SELECT COUNT(*) FROM Student) as ST, \r\n"
+ " (SELECT COUNT(*) FROM Teacher) as TE,\r\n"
+ " (SELECT COUNT(*) FROM Subject) as SU", nativeQuery = true)
public Map<String,Integer> getCount();
Finally the serviceImpl--
public CountDto getCount{
CountDto CountValue = new CountDto();
Map<String,Integer> map = repository.getCount();
for (Map.Entry<String,Integer> entry : map.entrySet()) {
if(entry.getKey().equals("ST"))
CountValue.setStudentTotal( entry.getValue());
if(entry.getKey().equals("TE"))
CountValue.setTeacherTotal( entry.getValue());
if(entry.getKey().equals("SU"))
CountValue.setSubjectTotal( entry.getValue());
}
return CountValue ;
}
But the solution seems quite complex to me. Any simpler approach required.
In pure Hibernate/JPA usage this is a simple dynamic-instantiation query (what JPA calls a "constructor result"):
select new CountDTO(
(SELECT COUNT(*) FROM Student) as ST,
...
)
No idea here about Spring, though a word of warning.. in trying to be useful, it often "gets in the way". Not sure that is the case here... Have you tried straight Hibernate/JPA?

Unable to pass a collection to a constructor via #Query

I am getting an SQL exception
java.sql.SQLException: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'as col_7_0_ from locales offerlocal0_ cross join offers offer2_ inner join offer' at line 1
While calling the repository method
#Query("SELECT DISTINCT new com.greenflamingo.staticplus.model.catalog.dto.OfferGet(ol.root.id,ol.title "
+ ",ol.description,dl.name,ol.root.price,ol.root.currency,ol.root.visible,ol.root.images) "
+ "FROM OfferLocale ol,DescriptorLocale dl "
+ "WHERE ol.root.webfront.id = (:webId) AND ol.culture.languageCode = (:langCode) "
+ "AND dl.culture.languageCode = (:langCode) "
+ "AND ol.root.category = dl.root")
Page<OfferGet> findAllWebfrontLocalized(#Param("webId")int webfrontId,#Param("langCode")String langCode,Pageable pageable );
I have narrowed the issue down to the Collection i am trying to pass to constructor (ol.root.images) . Tried with List (it gave me a constructor missmatch) and with Set (had the same error as shown here)
This is the bean i am using
public class OfferGet implements Serializable{
private static final long serialVersionUID = 6942049862208633335L;
private int id;
private String title;
private String shortDescription;
private String price;
private String category;
private boolean visible;
private List<Image> images;
public OfferGet(String title, String category) {
super();
..........
}
public OfferGet() {
super();
}
public OfferGet(int id, String title, String description
, BigDecimal price
,String currency,
boolean visible) {
.........
}
public OfferGet(int id, String title, String description,String category
, BigDecimal price
,String currency,
boolean visible,
Collection<Image> images) {
..........
}
}
I am using java 11, mariaDb and Springboot 2.0.5
Does anyone know why is this happening and if there is any way around it? Any help would be much appreciated, mustache gracias! :D
It's not possible to create an object with the constructor expression that takes a collection as argument.
The result of a SQL query is always a table.
The reason is that identification variables such that they represent instances, not collections.
Additionally you cannot return root.images you must join the OneToMany relationship and then you no longer have a collection but each attribute.
The result of the query will be cartesian product.
This is a correct query:
#Query("SELECT DISTINCT new com.greenflamingo.staticplus.model.catalog.dto.OfferGet(ol.root.id,ol.title "
+ ",ol.description,dl.name,ol.root.price,ol.root.currency,ol.root.visible, image) "
+ "FROM OfferLocale ol,DescriptorLocale dl "
+ "JOIN ol.root.images image
+ "WHERE ol.root.webfront.id = (:webId) AND ol.culture.languageCode = (:langCode) "
+ "AND dl.culture.languageCode = (:langCode) "
+ "AND ol.root.category = dl.root")

How to write JpaRepository method for searching Users that have phone number stored in Set<String>

I'm working on a Spring project and now i have to write JPA method for searching a users that have phone number LIKE in Set
The method should be in my interface UserRepository that implements JpaRepository
I have tried to write the method like:
List<User> findByNameLikeOrPhoneNumbersLike(String name, Set<String> phoneNumbers);
List<User> findByNameLikeOrPhoneNumbersLike(String name, String phoneNumber);
List<User> findByNameLikeOrPhoneNumbersContaining(String name, String phoneNumber);
But none of them works.
My class for the User Entity is:
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
#Convert(converter = PhoneNumbersConverter.class)
public Set<String> phoneNumbers = new TreeSet<>();
........
}
Currently my PhoneNumbersConverter converts the list to joined String with ","
I want to write method that can search me Users on given string that may be the LIKE the name or may be LIKE one of the user Numbers.
If you're ok with using nativeQuery #Query you could do it like that (assuming that column names are name, phone_numbers and that phone_numbers looks like this 111222333,222333444,333444555).
#Query(value =
"SELECT _user FROM user _user " +
"WHERE " +
"_user.name LIKE CONCAT('%', $1, '%') OR " +
"_user.phone_numbers LIKE CONCAT('%', $1, '%')",
nativeQuery = true
)
List<User> findWhereNameOrPhoneNumberLike(String query);
You also might want to use UPPER function for both query and _user.name to be case-insensitive (UPPER(_user.name) LIKE UPPER(CONCAT('%', $1, '%'))).

Use Enum as parameter in Named Query

I have a named query that selects all the record that have a search string in it.
This is my NamedQuery.
#NamedQuery(
name = "findAllPersonBySearch",
query = "SELECT p FROM Person p "
+ "WHERE LOWER(p.pvId) LIKE LOWER(:searchString) "
+ "OR LOWER(p.firstName) LIKE LOWER(:searchString) "
+ "OR LOWER(p.middleName) LIKE LOWER(:searchString) "
+ "OR LOWER(p.lastName) LIKE LOWER(:searchString) "
+ "OR p.birthDate LIKE :searchString"
)
The record contains 2 enum in it. One is Gender and the Other is Person Type. Here are my enums:
PersonType
package ph.com.smesoft.hms.reference;
public enum PersonType {
Customer, Personnel
}
Gender
package ph.com.smesoft.hms.reference;
public enum Gender {
Male, Female
}
I have a search box in my list view which returns a string whenever the Search button is clicked.
How do I use the enums as parameters in my Named Query?
I have tried this:
ph.com.smesoft.hms.reference.PersonType.Personnel = :searchString
But nothing happened. Hope someone can help me out on this!
UPDATE:
This is where the method that accepts the string passed from the Controller and sets it as query parameter:
public List<Person> findAllBySearch(String searchString){
TypedQuery<Person> searchResult = em.createNamedQuery("findAllPersonBySearch", Person.class);
searchResult.setParameter("searchString",'%'+searchString+'%');
List<Person> result=searchResult.getResultList();
return result;
}
and this is my controller method that accepts the string typed from the view:
Controller Method
#RequestMapping(value = "/search", method = { RequestMethod.GET })
public String listofFloor(#ModelAttribute("SearchCriteria") SearchForm searchForm, Model uiModel) {
uiModel.addAttribute("people", personService.findAllBySearch(searchForm.getSearchString()));
return "people/list";
}

Spring data jpa, jparepository returning list of string in place of DTO object

I have a interface implementing JPARepository and have three methods, one of them is having a custom #Query.
public interface PersonRepository extends JpaRepository<Person, Long> {
List<Person> getPersonBycountryCode(String countryCode);
List<Person> findByCountryCodeAndCity(String string,String city);
#Query(value = "SELECT person.firstName as firstName, person.lastName as lastName, person.countryCode as country, person.city as city,"
+ " SQRT(POWER((69.1 * (person.age - :age )) , 2 )"
+ " + POWER((53 * (person.experience - :experience )), 2)) as eligibility"
+ " FROM Person person"
+ " ORDER BY eligibility ASC")
List<PersonDetailsDto> findPersonDetailsByEligibility(
#Param("age") BigDecimal age,
#Param("experience") BigDecimal experience,
Pageable pageable
);
}
Problem is: method with #Query does not return list of PersonDetailsDto but return list of list of strings (List<List<String>>).
PersonDetailsDto is a POJO class with all the variables described in a query output (firstName, lastName, country, city, eligibility) and also a constructor with all the variables as Parameters. Other two methods does return list of Person object.
Any idea?
Actually JpaRepository<Person, Long> means that, you can use only Person as your dto in jpa repository methods.
For your solution you can just define your dto interface inside the repository :
public interface PersonRepository extends JpaRepository<Person, Long> {
List<Person> getPersonBycountryCode(String countryCode);
List<Person> findByCountryCodeAndCity(String string,String city);
#Query(value = "SELECT person.firstName as firstName, person.lastName as lastName, person.countryCode as country, person.city as city,"
+ " SQRT(POWER((69.1 * (person.age - :age )) , 2 )"
+ " + POWER((53 * (person.experience - :experience )), 2)) as eligibility"
+ " FROM Person person"
+ " ORDER BY eligibility ASC")
List<PersonDetailsDto> findPersonDetailsByEligibility(
#Param("age") BigDecimal age,
#Param("experience") BigDecimal experience,
Pageable pageable
);
//define the interface here
public interface PersonDetailsDto{
public String getFirstName();
public String getLastName();
public String getCountry();
public String getCity();
public Integer getEligibility();
}
}
If I am not wrong the idea behind JPA not looking for specific fields is that is cost (efficiency wise) the same to bring one column or all columns from one row of the table.But to solve your problem you can set nativeQuery = true in the #Query annotation from a Repository class like this:
public static final String FIND_SOMETHING = "SELECT somethingId, somethingName FROM something";
#Query(FIND_SOMETHING, nativeQuery = true)
public List<Object[]> findSomethings();
I hope this will help you to resolve your problem.
You can use new keyword in query of #Query. And make sure you have the appropriate constructor for PersonDetailsDto and also change package name.
#Query(value = "SELECT new com.company.PersonDetailsDto(person.firstName, person.lastName, person.countryCode , person.city ,"
+ " SQRT(POWER((69.1 * (person.age - :age )) , 2 )"
+ " + POWER((53 * (person.experience - :experience )), 2)) "
+ " FROM Person person"
+ " ORDER BY eligibility ASC")
List<PersonDetailsDto> findPersonDetailsByEligibility(
#Param("age") BigDecimal age,
#Param("experience") BigDecimal experience,
Pageable pageable
);
Similar question's answer.
just call it by its alias, it worked for me like that
ex :
#Query(value = "SELECT person FROM Person person"
+ " ORDER BY eligibility ASC")
List<PersonDetailsDto> findPersonDetailsByEligibility(
#Param("age") BigDecimal age,
#Param("experience") BigDecimal experience,
Pageable pageable
);

Categories

Resources