How to dynamically select columns in Spring Data JPA - spring-boot

I have a typical Spring boot(2.7.6) application with apis' to fetch data in Kotlin.
Assume an entity called Employee
#Entity
data class Employee(
val id: Long,
val name: String,
val age: Int,
val interviewDate: LocalDate,
val joiningDate: LocalDate,
val resignationDate: LocalDate,
val lastWorkingDate: LocalDate
)
For brevity I have removed annotations like #Id etc from above entity class.
One of the APIs which vends out Employee data is such that, in request params I get something like dateType and it will have one of interviewDate/joiningDate/resignationDate/lastWorkingDate. And in request params dateFrom and dateTo, I get the date as an input like 2020-10-01 and 2022-12-30
For example, if api gets input like dateType=interviewDate&dateFrom=2020-10-01&dateTo=2022-12-30 then API has to return all the employee records whose interview_date column has values between 2020-10-01 and 2022-12-30
The example given above is just for ease of explaining. For real use-case have to fetch data from many tables and has many joins(inner/left/right).
Based on the input, what is the better way to select columns dynamically in repository method?
I tried Specification Criteria API, but it was a dead end because I cannot use joins as there is no mapping between Entities like #OneToMany etc.
I am trying with #Query to get data but have to duplicate lots of lines of sql for each condition.
Example of one of the queries I have written in repository class is like below:
#Query(
"""
select
t.a as A,
t.b as B,
tt.c as C,
p.d as D,
p.e as E
from Employee p
join Department t on p.some_id = t.id
join PersonalData tt on tt.id = t.some_id
left outer join SalaryInformation ps on p.id = ps.come_id
left outer join ManagerInformation sbt on p.some_id = sbt.id
. few more joins here
.
.
where p.id= :id and p.interviewDate>=:dateFrom and p.interviewDate<=:dateTo
""" ,
nativeQuery = true
)
fun findByEmployeeIdForInterviewDate(employeeId: Long, dateFrom:String, dateTo: String, pageable: Pageable): Slice<EmployeeDetailsProjection>
With current approach, I have to repeat this query for remaining date columns which I dont want to as it looks ugly.
Any better suggestions will be really helpful :)

I tried Specification Criteria API, but it was a dead end because I cannot use joins as there is no mapping between Entities like #OneToMany etc.
Hibernate 6 introduces a JPA Criteria extension API which you can use to model joins to entities. To use Hibernate 6 you will have to update to Spring 3 though. For every JPA Criteria interface, Hibernate introduces a sub-interface prefixed with Jpa that contains the Hibernate JPA Criteria extensions. You could do e.g.
JpaEntityJoin<SalaryInformation> ps = ((JpaRoot<?>) root).join(SalaryInformation.class, SqmJoinType.LEFT);
ps.on(cb.equal(root.get("id"), ps.get("comeId")));

Related

JPQL, Can I Create DTO using Constructor which Takes Entities by Selecting All Columns In Once?

I'm using Spring Data JPA and Hibernate. I made repository having a method returning List<AWithBDto> like
#Query(
"""
select new com.test.example.AWithBDto(a.a1, a.a2, ..., b.b1, b.b2, ...)
from A a join B b where ~
order by ~
"""
)
Because I thought listing all columns of table a and b is tedious, I added a constructor in AWithBDto as follows. (in Kotlin)
data class AWithBDto(
...
) {
constructor(a: A, b: B) : this(
a1 = a.a1,
a2 = a.a2,
...,
b1 = b.b1,
b2 = b.b2,
...,
)
}
So I could shorten my repository codes as follows.
#Query(
"""
select new com.test.example.AWithBDto(a, b)
from A a join B b where ~
order by ~
"""
)
However, the query made kind of N+1 problems because it selected only id columns from both tables.
select
a0_.id as col_0_0_,
b1_.id as col_1_0_
from
a a0_
inner join
b b1_
on a0_.id=b1_.id
where
~
order by
~
and multiple queries selecting all columns for each single row from each table were executed.
select
a0_.id as id1_3_0_,
...,
...,
from
a a0_
where
a0_.id=?
...multiple times
select
b0_.id as id1_4_0_,
...,
...,
from
b b0_
where
b0_.id=?
...multiple times
In JPQL, Can I create DTO using a constructor which takes entities as params by selecting all columns in once? I want Hibernate to execute a single query for this.
Or if I can't, could you suggest good alternatives for me?
I think this is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(A.class)
public interface AWithBDto {
#IdMapping
Long getId();
String getA1();
String getA2();
// Either map properties through nested mapping path expressions
#Mapping("b.b1")
String getB1();
#Mapping("b.b2")
String getB2();
// Or through a subview
BDto getB();
#EntityView(B.class)
interface BDto {
#IdMapping
Long getId();
String getB1();
String getB2();
}
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
AWithBDto a = entityViewManager.find(entityManager, AWithBDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<AWithBDto> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!

Is there a way with spring data jdbc avoid n+1 issue?

When I run this query, I experience n+1 issue where data JDBC fetches all the related entities of the campaign objects. Is there any way to avoid this using data JDBC?
#Query(
"""
SELECT campaign.*
FROM campaign
JOIN targeting ON campaign.targeting_id = targeting.id
WHERE (
CASE
WHEN campaign.applications_close_date IS NOT NULL
THEN NOW() BETWEEN campaign.start_date AND campaign.applications_close_date
WHEN campaign.end_date IS NOT NULL
THEN NOW() BETWEEN campaign.start_date AND campaign.end_date
ELSE NOW() >= campaign.start_date
END
)
AND NOT EXISTS
(
SELECT *
FROM application
WHERE application.campaign = campaign.id
AND application.influencer = :influencerId
)
"""
)
fun findAllMatchingByInfluencerId(
influencerId: Long,
country: String?
): List<Campaign>
Yes, you can.
If you don't need the referenced entities you should use a class that doesn't have those properties as a return value.
If you actually want those referenced entities, but have a more efficient way to construct the entity with its references you may specify your own ResultSetExtractor or RowMapper in the #Query annotation.

Spring JPA #Query Annatotion disadvantages

Hi I use spring jpa to access my data in my spring boot project.I am wondering that is there any difference between #Query annatotation and critearia api in jpa.Are they totaly same or is there any difference(Their writing styles are different ,and I mean any performance or other issue between them)
Mostly I prefer #Query annotation it looks simple.Or any other option some one can advice like #Query or criteria api in spring jpa.And is there any disadvantages of #Query style?
#Query("SELECT u FROM User u WHERE u.status = 1")
Collection<User> findAllActiveUsers();
List<Book> findBooksByAuthorNameAndTitle(String authorName, String title) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Book> cq = cb.createQuery(Book.class);
Using #Query we can pass static query or pre compiled query so we can perform both select and non-select operations on the data
where as Criteria is suitable for executing Dynamic Queries such requirements occurs when data are know at run time
but using criteria api we can only perform select operations on the data.
For example
#Query(value = "SELECT u FROM User u")
List<User> findAllUsers(Sort sort);
We can also work with pre compiled query using #Query
For Example
#Query("SELECT u FROM User u WHERE u.status = :status and u.name = :name")
User findUserByStatusAndNameNamedParams(
  #Param("status") Integer status,
  #Param("name") String name);)
Dynamic queries like
Criteria cr = session.createCriteria(Employee.class);
// To get records having salary more than 2000
cr.add(Restrictions.gt("salary", 2000));
// To get records having salary less than 2000
cr.add(Restrictions.lt("salary", 2000));
Actual use of Dynamic queries comes when we'll encounter the need for building SQL statements based on conditions or data sets whose values are only known at runtime. And, in those cases, we can't just use a static query So we can't just use the #Query annotation since we can't provide a static SQL statement.In such case we use Criteria API
For more info follow the link provided
#Query and Criteria

Spring - aggregation - feed activity

I am working with spring and a Postgresql database.
I have a Event table (exists differents types of events)
I have a sql query to shows events to one user, similar like this:
#Query( value = "SELECT e FROM Event e WHERE e.user.id = :userId ORDER BY e.date DESC")
Page<Event> findEvents(Long userId, Pageable pageable);
I would like to be able to aggregate events per type, when the dates are very close.
Something like this: "Your photo has 10 likes"
How do you solve these types of cases?
If I understand your problem correctly, Hibernates #Formular Annotation should be appropriate.
It could look like this:
#Formula(“(select count(b.someData) from table b where b.key = key)”)
private Integer someData;
https://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/#d0e2785

Inner Join and Group By using Specification in Spring Data JPA

I am trying to fetch the employee details whose empltype is clerk and whose joining date is the recent one.
For which the query looks like following in SQL Server 2008:
select
*
from
employee jj
inner join
(
select
max(join_date) as jdate,
empltype as emptype
from
employee
where
empltype='clerk'
group by empltype
) mm
on jj.join_date=mm.jdate and jj.empltype=mm.emptype;
I am using SpringData JPA as my persistence layer using QuerylDSL,Specification and Predicate to fetch the data.
I am trying to convert the above query either in QueryDSL or Specification, but unable to hook them properly.
Employee Entity :
int seqid;(sequence id)
String empltype:
Date joindate;
String role;
Predicate method in Specifcation Class :
Predicate toPredicate(Root<employee> root,CriteriaQuery <?> query,CriteriaBuilder cb)
{
Predicate pred=null;
// Returning the rows matching the joining date
pred=cb.equal(root<Emplyoee_>.get("joindate"));
//**//
}
What piece of code should be written in //**// to convert about SQL query to JPA predicate. any other Spring Data JPA impl like #Query,NamedQuery or QueryDSL which returns Page also works for me.
Thanks in advance
I wrote this in notepad and it hasn't been tested but I think you're looking for something like
QEmployee e1 = new QEmployee("e1");
QEmployee e2 = new QEmployee("e2");
PathBuilder<Object[]> eAlias = new PathBuilder<Object[]>(Object[].class, "eAlias");
JPASubQuery subQuery = JPASubQuery().from(e2)
.groupBy(e2.empltype)
.where(e2.empltype.eq('clerk'))
.list(e2.join_date.max().as("jdate"), e2.emptype)
jpaQuery.from(e1)
.innerJoin(subQuery, eAlias)
.on(e1.join_date.eq(eAlias.get("jdate")), e1.emptype.eq(eAlias.get("emptype")))
.list(qEmployee);

Resources