Spring data JPA query with parameter properties - spring

What is the simplest way of declaring a Spring data JPA query that uses properties of an input parameter as query parameters?
For example, suppose I have an entity class:
public class Person {
#Id
private long id;
#Column
private String forename;
#Column
private String surname;
}
and another class:
public class Name {
private String forename;
private String surname;
[constructor and getters]
}
... then I would like to write a Spring data repository as follows:
public interface PersonRepository extends CrudRepository<Person, Long> {
#Query("select p from Person p where p.forename = ?1.forename and p.surname = ?1.surname")
findByName(Name name);
}
... but Spring data / JPA doesn't like me specifying property names on the ?1 parameter.
What is the neatest alternative?

This link will help you: Spring Data JPA M1 with SpEL expressions supported. The similar example would be:
#Query("select u from User u where u.firstname = :#{#customer.firstname}")
List<User> findUsersByCustomersFirstname(#Param("customer") Customer customer);
https://spring.io/blog/2014/07/15/spel-support-in-spring-data-jpa-query-definitions

Define the query method with signatures as follows.
#Query(select p from Person p where p.forename = :forename and p.surname = :surname)
User findByForenameAndSurname(#Param("surname") String lastname,
#Param("forename") String firstname);
}
For further details, check the Spring Data JPA reference

What you want is not possible. You have to create two parameters, and bind them separately:
select p from Person p where p.forename = :forename and p.surname = :surname
...
query.setParameter("forename", name.getForename());
query.setParameter("surname", name.getSurname());

You could also solve it with an interface default method:
#Query(select p from Person p where p.forename = :forename and p.surname = :surname)
User findByForenameAndSurname(#Param("surname") String lastname,
#Param("forename") String firstname);
default User findByName(Name name) {
return findByForenameAndSurname(name.getLastname(), name.getFirstname());
}
Of course you'd still have the actual repository function publicly visible...

You can try something like this:
public interface PersonRepository extends CrudRepository<Person, Long> {
#Query("select p from Person AS p"
+ " ,Name AS n"
+ " where p.forename = n.forename "
+ " and p.surname = n.surname"
+ " and n = :name")
Set<Person>findByName(#Param("name") Name name);
}

if we are using JpaRepository then it will internally created the queries.
Sample
findByLastnameAndFirstname(String lastname,String firstname)
findByLastnameOrFirstname(String lastname,String firstname)
findByStartDateBetween(Date date1,Date2)
findById(int id)
Note
if suppose we need complex queries then we need to write manual queries like
#Query("SELECT salesOrder FROM SalesOrder salesOrder WHERE salesOrder.clientId=:clientId AND salesOrder.driver_username=:driver_username AND salesOrder.date>=:fdate AND salesOrder.date<=:tdate ")
#Transactional(readOnly=true)
List<SalesOrder> findAllSalesByDriver(#Param("clientId")Integer clientId, #Param("driver_username")String driver_username, #Param("fdate") Date fDate, #Param("tdate") Date tdate);

The simplicity of Spring Data JPA is that it tries to interpret from the name of the function in repository without specifying any additional #Query or #Param annotations.
If you are supplying the complete name, try to break it down as firstname and lastname and then use something like this -
HotelEntity findByName(String name);
My HotelEntity does contain the field name so JPA tries to interpret on its own to infer the name of the field I am trying to query on and create a subsequent query internally.
Some more evidence from JPA documentation -
Further details - here

Are you working with a #Service too? Because if you are, then you can #Autowired your PersonRepository to the #Service and then in the service just invoke the Name class and use the form that #CuriosMind... proposed:
#Query(select p from Person p where p.forename = :forename and p.surname = :surname)
User findByForenameAndSurname(#Param("surname") String lastname,
#Param("forename") String firstname);
}
and when invoking the method from the repository in the service, you can then pass those parameters.

for using this, you can create a Repository for example this one:
Member findByEmail(String email);
List<Member> findByDate(Date date);
// custom query example and return a member
#Query("select m from Member m where m.username = :username and m.password=:password")
Member findByUsernameAndPassword(#Param("username") String username, #Param("password") String password);

#Autowired
private EntityManager entityManager;
#RequestMapping("/authors/{fname}/{lname}")
public List actionAutherMulti(#PathVariable("fname") String fname, #PathVariable("lname") String lname) {
return entityManager.createQuery("select A from Auther A WHERE A.firstName = ?1 AND A.lastName=?2")
.setParameter(1, fname)
.setParameter(2, lname)
.getResultList();
}

Related

Adding and removing dynamically SQL WHERE clause using JPA [duplicate]

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

Get fields of spring jpa interface projection

I have this stored procedure that I am calling in spring jpa repository and I am using interface based projection.
Whenever I try to call the interface projection method i get this error
Invoked method public abstract java.lang.Long
ConfirmationDTO.memberID() is no accessor method!
Here is my projection Interface
public interface ConfirmationDTO {
Long memberID();
LocalDate dateEntry();
}
and the DAO
#Query(value=" CALL get_confirmation(:startDate) ", nativeQuery=true)
List<ConfirmationDTO> getConfirmation(LocalDate startDate);
Is it possible to get the field values from the interface projection ?
I found another SO thread which uses Tuple, this helped me to achieve the goal of my above question.
how-to-map-sql-native-query-result-into-dto-in-spring-jpa-repository
Here is the sample code from that thread :
#Repository
public interface StockRepository extends RevisionRepository<Stock, Long, Integer>, JpaRepository<Stock, Long> {
#Query(value = "SELECT stock_akhir.product_id AS productId, stock_akhir.product_code AS productCode, SUM(stock_akhir.qty) as stockAkhir "
+ "FROM book_stock stock_akhir "
+ "where warehouse_code = (:warehouseCode) "
+ "AND product_code IN (:productCodes) "
+ "GROUP BY product_id, product_code, warehouse_id, warehouse_code", nativeQuery = true)
List findStockAkhirPerProductIn(#Param("warehouseCode") String warehouseCode, #Param("productCodes") Set productCode); }
and them map the Tuple in the service:
public List<StockTotalResponseDto> findStocktotal() {
List<Tuple> stockTotalTuples = stockRepository.findStocktotal();
List<StockTotalResponseDto> stockTotalDto = stockTotalTuples.stream()
.map(t -> new StockTotalResponseDto(
t.get(0, String.class),
t.get(1, String.class),
t.get(2, BigInteger.class)
))
.collect(Collectors.toList());
return stockTotalDto;
}
Let me try to explain how you can easily do this.
public class Confirmation {
private Long memberId;
private LocalDate dateEntry;
//add other fields
//provide getters and setters
}
//tuple
public inteface ConfirmationTuple {
Long getMemberId ();
LocalDate getDateEntry ();
}
//Your repository
#Query(value = " CALL get_confirmation(:startDate) ", nativeQuery = true)
List<ConfirmationTuple> getConfirmation (LocalDate startDate);
Spring will do the rest for you. To get the memberId from the first tuple, all you do is
yourDAO.getConfirmation(startDate).get(0).getMemberId();
The catch here is the get methods in your tuple must correspond to the field names being returned by your query in your repository. For example, if your query is returning the following columns [memberName,myDate] your Tuple interface must have getMemberName and getMyDate() for these values to be assigned.
You could create an implementation component and it would be autowired, but it's not recommended to annotate DTO classes.
The easiest way is to turn your interface into a class.
At the end of the day it's just a DTO, it have no logic, and on tests you can mock it as you wish just filling the properties.
I don't see the point on your DTO being an interface, unless a Class somewhere is implementing more than one interface and this one is among them.
If this is the case, I would rethink the implementation - e.g. implement TheOtherInterface extend Person.

Returning two diffrent data types for one method of CrudRepository (findById)

Im using CrudRepository.
I need method findById in two scenarios:
EmailShort findById(Long id);
EmailFull findById(Long id);
EmailShort and EmailFull are interfaces.
public interface EmailFull extends EmailCustom {
Long getId();
UserShort getSender();
String getContent();
String getTopic();
String getShortContent();
#JsonFormat
(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy HH:mm:ss")
Date getCreationTime();
}
public interface EmailShort extends EmailCustom {
Long getId();
UserShort getSender();
String getTopic();
String getShortContent();
boolean getIsRead();
void setRead(boolean read);
#JsonFormat
(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy HH:mm:ss")
Date getCreationTime();
}
My Repository:
#Repository
public interface EmailDao extends CrudRepository<Email, Long> {
EmailShort findById(Long id);
#Query("select e from email e where e.id=?1")
EmailFull findFullById(#Param("id") Long id);
#Query(value = "select * " +
"from email " +
"where email.id = :emailId", nativeQuery = true)
EmailShort findByIdWithShortSenderInfo(#Param("emailId") long emailId);
}
I can't use Email object, beacuse it contains User with data like address etc (doesn't need it).
Any ideas how to solve this problem? Maybie I should change entities definitions?
You simply have to use a different name. This is not related to Spring Data at all.
Java decides the method uniqueness based on the signature of the method - name and parameters, not including the return types.
Read up covariance and contravariance for better understanding, plus this: https://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.4.2
I would use projections in this case: https://docs.spring.io/spring-data/jpa/docs/2.1.4.RELEASE/reference/html/#projections

spring boot data #query to DTO

I want to assign the result of a query to a DTO object. The DTO looks like this:
#Getter
#Setter
#NoArgsConstructor
public class Metric {
private int share;
private int shareholder;
public Metric(int share, int shareholder) {
this.share = share;
this.shareholder = shareholder;
}
}
And the query looks like the following:
#RepositoryRestResource(collectionResourceRel = "shareholders", path = "shareholders")
public interface ShareholderRepository extends PagingAndSortingRepository<Shareholder, Integer> {
#Query(value = "SELECT new com.company.shareholders.sh.Metric(SUM(s.no_of_shares),COUNT(*)) FROM shareholders s WHERE s.attend=true")
Metric getMetrics();
}
However, this didn't work, as I got the following exception:
Caused by:org.hibernate.QueryException: could not resolve property: no_of_shares of:com.company.shareholders.sh.Shareholder[SELECT new com.company.shareholders.sh.Metric(SUM(s.no_of_shares),COUNT(*)) FROM com.company.shareholders.sh.Shareholder s WHERE s.attend=true]
In my project I've used projections to this like shown below:
#Repository
public interface PeopleRepository extends JpaRepository<People, Long> {
#Query(value = "SELECT p.name AS name, COUNT(dp.people_id) AS count " +
"FROM people p INNER JOIN dream_people dp " +
"ON p.id = dp.people_id " +
"WHERE p.user_id = :userId " +
"GROUP BY dp.people_id " +
"ORDER BY p.name", nativeQuery = true)
List<PeopleDTO> findByPeopleAndCountByUserId(#Param("userId") Long userId);
#Query(value = "SELECT p.name AS name, COUNT(dp.people_id) AS count " +
"FROM people p INNER JOIN dream_people dp " +
"ON p.id = dp.people_id " +
"WHERE p.user_id = :userId " +
"GROUP BY dp.people_id " +
"ORDER BY p.name", nativeQuery = true)
Page<PeopleDTO> findByPeopleAndCountByUserId(#Param("userId") Long userId, Pageable pageable);
}
The interface to which the result is projected:
public interface PeopleDTO {
String getName();
Long getCount();
}
The fields from the projected interface must match the fields in this entity. Otherwise field mapping might break.
Also if you use SELECT table.column notation always define aliases matching names from entity as shown in example.
In your case change #Query like shown below:
#Query(value = "SELECT new " +
"SUM(s.no_of_shares) AS sum,COUNT(*) AS count FROM " +
"shareholders s WHERE s.attend=true", nativeQuery = true)
MetricDTO getMetrics();
And create interface MetricDTO like shown below:
public interface MetricDTO {
Integer getSum();
Long getCount();
}
Also make sure the return type of getSum() and getCount() is correct this may vary based not database.
First, you can have a look at the Spring Data JPA documentation, you can find some help at this section : Class-based Projections (DTOs).
There is also a paragraph titled Avoid boilerplate code for projection DTOs, where they advise you to use Lombok's #Value annotation, to produce an immutable DTO. This is similar to Lombok's #Data annotation, but immutable.
If you apply it to your example, the source will look like :
#Value
public class MetricDto {
private int share;
private int shareholder;
}
Then, as your query is a NativeQuery, specifiy it in your Spring Data Repository.
You can find help in the documentation : Native Queries.
You will need something like :
#Query(value = "SELECT new
com.company.shareholders.sh.MetricDto(SUM(s.no_of_shares),COUNT(*)) FROM
shareholders s WHERE s.attend=true", nativeQuery = true)
MetricDto getMetrics();
Query query = sessionFactory.getCurrentSession()
.createNativeQuery(stringQuery).unwrap(org.hibernate.query.Query.class);
((NativeQueryImpl) query).setResultTransformer(new AliasToBeanResultTransformer(DtoClass.class));
You are writing a mixed query of native and jpql; no_of_shares is your column name in the database, but jpa is expecting you to provide not native syntax so try to replace no_of_shares with the corresponding field in your entity class. Or just add nativeQuery = true to make jpa understand it's a native query.

JPA #Query annotation does not seem to be using paramter

I have a Spring Boot application using JPA/Hibernate as entity management/modeling. I have the following user class:
#Entity
public class User {
#Id
private Long Id;
private String name;
//more fields, getters and setters below
}
I want users of my application to be able to search for users by name. So in my repository interface, I have:
public interface UserRepository extends JpaRepository<User, Long> {
#Query(value = "SELECT u from User u WHERE lower(u.name) LIKE lower(:name)")
List<User> findByNameLike(#Param(value="name") String nmae);
}
Then in my UserController, I have:
#GetMapping(value = "/users/")
public #ResponseBody List<User> search(#RequestParam String name) {
return this.userRepository.findByNameLike(name);
}
This always returns an empty list. I know the name parameter is coming in correctly.
Moreover, I do know that it is recognizing the #Query annotation, because if I change the query to something like
SELECT u FROM User u
it will return me all users. Or if I change it to
SELECT u from User u WHERE u.name = '%Bob%'
and remove the parameter from the method, it will return all users whose name is Bob. I've tried other variations of the query, such as
SELECT u FROM User u WHERE lower(u.name) LIKE CONCAT('%', lower(:name), '%')
SELECT u FROM User u WHERE u.name = :name (even a simple equals doesn't work)
and so on and so forth. Every source I look at whose syntax I copy seems to say I'm writing the query right, so I think something else must be going on.
I've also tried letting Spring generate the query for me, using
public List<User> findByNameLike(String name);
public List<User> findByNameContaining(String name);
which also don't seem to work. Any help would be appreciated here!

Resources