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

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.

Related

How to dynamically select columns in Spring Data JPA

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")));

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

SimpleJpaRepository Count Query

I've modified an existing RESTful/JDBC application i have to work with new features in Spring 4... specifically the JpaRepository. It will:
1) Retrieve a list of transactions for a specified date. This works fine
2) Retrieve a count of transactions by type for a specified date. This is not working as expected.
The queries are setup similarly, but the actual return types are very different.
I have POJOs for each query
My transactions JPA respository looks like:
public interface MyTransactionsRepository extends JpaRepository<MyTransactions, Long>
//My query works like a charm.
#Query( value = "SELECT * from ACTIVITI_TMP.BATCH_TABLE WHERE TO_CHAR(last_action, 'YYYY-MM-DD') = ?1", nativeQuery = true )
List< MyTransactions > findAllBy_ToChar_LastAction( String lastActionDateString );
This returns a list of MyTransactions objects as expected. Debugging, i see the returned object as ArrayList. Looking inside the elementData, I see that each object is, as expected, a MyTransactions object.
My second repository/query is where i'm having troubles.
public interface MyCountsRepository extends JpaRepository<MyCounts, Long>
#Query( value = "SELECT send_method, COUNT(*) AS counter FROM ACTIVITI_TMP.BATCH_TABLE WHERE TO_CHAR(last_action, 'YYYY-MM-DD') = ?1 GROUP BY send_method ORDER BY send_method", nativeQuery = true )
List<MyCounts> countBy_ToChar_LastAction( String lastActionDateString );
This DOES NOT return List as expected.
The object that holds the returned data was originally defined as List, but when I inspect this object in Eclipse, I see instead that it is holding an ArrayList. Drilling down to the elementData, each object is actually an Object[2]... NOT a MyCounts object.
I've modified the MyCountsRepository query as follows
ArrayList<Object[]> countBy_ToChar_LastAction( String lastActionDateString );
Then, inside my controller class, I create a MyCounts object for each element in List and then return List
This works, but... I don't understand why i have to go thru all this?
I can query a view as easily as a table.
Why doesn't JPA/Hibernate treat this as a simple 2 column table? send_method varchar(x) and count (int or long)
I know there are issues or nuances for how JPA treats queries with counts in them, but i've not seen anything like this referenced.
Many thanks for any help you can provide in clarifying this issue.
Anthony
That is the expected behaviour when you're doing a "group by". It will not map to a specific entity. Only way this might work is if you had a view in your database that summarized the data by send_method and you could map an entity to it.

LINQ "equals" operator on nullable foreign key in NHibernate parent/child relationships

The background
My model looks like the following: (writing fields instead of properties for simplicity)
public class Entity {
public long Id;
public string Name;
public Entity Parent;
}
Essential FNH mapping
Map(x => x.Name)
.Not.Nullable()
.UniqueKey("Child");
References(x => x.Parent)
.Cascade.None()
.UniqueKey("Child");
SQL
create table `Entity` (Id BIGINT not null, Name VARCHAR(255) not null, Parent_id BIGINT, primary key (Id),unique (Name, Parent_id))
And that's fine. I don't want omonimities between children of the same entities (so two entities of different parent may have same name). By the way, bear in mind that Parent_id is nullable
What I need to do
I want to enforce a check before inserting a new entity into the DB. Instead of catching an exception I want to fire a query (but I think it reduces performance...) to check if an entity with same name and parent of newcoming exists in order to throw a decent exception. Despite performance, it's still a chance to learn something new about LINQ providers
In plain old SQL I would do
SELECT Id FROM entity WHERE Name = ? AND Parent_id = ?
and this correctly supports NULL ids
What I tried (and failed, otherwise I wouldn't be here)
var exInput = (from Entity entity in entityRepository.Query()
where entity.Name.ToLowerInvariant() == _newEntity.Name.ToLowerInvariant()
&& entity.Parent.Equals(_newEntity.Parent)
select new { ParentName = entity.Parent != null ? entity.Parent.Name : null }).FirstOrDefault();
I thought NHibernate could be smart enough to accept a null value as _newEntity.Parent and also smart enough to read entity.Parent.Equals as an expression instead of a method call (which fails in case of null).
Anyway that's not the problem
The error
System.NotSupportedException: Boolean Equals(System.Object)
I know NHibernate LINQ is not a full LINQ provider and doesn't support all methods Entity Framework supports. So I could expect that. Obviously, I can workaround by first selecting entity by name and then check if both parents are null or if they Equals() (I overloaded Equals to check Id)
The question
Given that I want NHibernate to generate SQL as closest as possible to the above WHERE clause, what should I do? Is there a different LINQ syntax to use or should I extend the LINQ provider?
I was thinking about extending LINQ provider, for which I have found some documentation. It is my opinion that if the operands of comparison are of the same identity we can simply match their ID (and if one of the entities is null generate NULL identity in HQL). In this case, did anyone try an implementation to share?
Don't use Equals in the query, just use entity.Parent == _newEntity.Parent.
Your Linq query also has a few additonal differences to the SQL you want to get. Why don't you simply use the following query?
var result = (from Entity entity in entityRepository.Query()
where entity.Name == _newEntity.Name && entity.Parent == _newEntity.Parent
select entity.Id).ToArray();

Is this linq query efficient?

Is this linq query efficient?
var qry = ((from member in this.ObjectContext.TreeMembers.Where(m => m.UserId == userId && m.Birthdate == null)
select member.TreeMemberId).Except(from item in this.ObjectContext.FamilyEvents select item.TreeMemberId));
var mainQry = from mainMember in this.ObjectContext.TreeMembers
where qry.Contains(mainMember.TreeMemberId)
select mainMember;
Will this be translated into multiple sql calls or just one? Can it be optimised? Basically I have 2 tables, I want to select those records from table1 where datetime is null and that record should not exist in table2.
The easiest way to find out if the query will make multiple calls is to set the .Log property of the data context. I typically set it to write to a DebugOutputWriter. A good example for this kind of class can be found here.
For a general way of thinking about it however, if you use a property of your class that does not directly map to a database field in a where clause or a join clause, it will typically make multiple calls. From what you have provided, it looks like this is not the case for your scenario, but I can't absolutely certain and suggest using the method listed above.

Resources