Get fields of spring jpa interface projection - spring

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.

Related

Custom update in CrudRepository

is that possible to returns updated entity by custom update method instead of numbers of affected rows? How can I achieve this?
I would like to have sth like this:
public interface DataRepository extends CrudRepository<Data, Long> {
#Modifying
#Query(value="UPDATE data SET max_version = max_version + 1 WHERE id = 'A'", nativeQuery=true)
Data updateDataByType();
}
instead of this
public interface DataRepository extends CrudRepository<Data, Long> {
#Modifying
#Query(value="UPDATE data SET max_version = max_version + 1 WHERE id = 'A'", nativeQuery=true)
Integer updateDataByType();
}
You cannot do this with the #Modifying annotation. because these methods can only be void and int. Otherwise you will get the error Modifying queries can only use void or int / Integer as return type.
But can be you can implement custom repository Implementation and return your updated entity after done with the query execution.
Reference: Custom Implementations for Spring Data Repositories

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.

How to Define Dynamic Model in Spring Framework

I am using Spring Framework as my back end
I have define know as Entity class The Entity class know contain 5 Fields
Below is the class , The code below dose not have setter getter part to make shorter and cleaner
#Entity
#Table(name="TblKnow")
public class Know {
#Id
private Double idKnow;
private String SubjectKnow;
private String BodyKnow;
private String ImgKnow;
private double CountView;
In JpaRepository interface i want to only query two column not all of columns.
public interface KnowRepository extends JpaRepository<Know,Double> {
#Query("SELECT idKnow,SubjectKnow FROM Know")
public Page<Know> findCByOrderByIdKnowDesc(Pageable pageable);
Problem: i try to run but i get below exception
java.lang.IllegalArgumentException: Cannot create TypedQuery for query with more than one return using requested result type [java.lang.Long]
But if i use without below query it is fine
public Page<Know> findAllByOrderByIdKnowDesc(Pageable pageable);
You can create a custom constructor and use that to select only some fields in JPA query.
public Know(Double idKnow, String SubjectKnow) {
this.idKnow = idKnow;
this.SubjectKnow = SubjectKnow;
}
And the use this constructor in JPA query. Make sure you use complete path of class with package.
#Query("SELECT NEW packagePath.Know(idKnow,SubjectKnow) FROM Know")
query :
public Page<Know> findAllByOrderByIdKnowDesc(Pageable pageable);
works dut to you select Know objects with fields that are mapped correct into Know class (and after wrapped into Page).
with query :
#Query("SELECT idKnow,SubjectKnow FROM Know")
public Page<Know> findCByOrderByIdKnowDesc(Pageable pageable);
returns some custome bean/object that spring data can't map in correct way into Know class (as you declared it as expected return class wrapped into Page). add counstructor into Know with idKnow,SubjectKnow fields , or you can wrap it into some DTO with idKnow,SubjectKnow fields.

Spring data JPA query with parameter properties

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();
}

Resources