I'm trying to translate SQL native query usage in #Query("...", native=true) Spring Data Jpa annotation, to JPQL query usage within the same annotation.
SQL Query:
select d.id as doctorId, d.firstName as doctorFirstName, d.lastName as doctorLastName, d.title as doctorTitle,
d.email as doctorEmail, v.id as id, v.dateFrom as dateFrom, v.dateTo as dateTo, v.status as status,
md.id as medicalServicesId, md.service as medicalServicesService, md.price as medicalServicesPrice
from Visit v
left outer join Doctor d on v.doctor_id=d.id
left outer join visit_medical_services vms on v.id=vms.medical_services_id
left outer join MedicalService md on vms.visit_id=md.id
where d.id= :doctorId and v.status= :status and v.dateFrom>= :dateFrom and v.dateTo<= :dateTo
I'm using Spring Projected Interface, that's why columns aliases are named liked this.
Now i want to get exactly the same result with JPQL - list of items. What i did tried it's this:
#Query("select d.id as doctorId, d.firstName as doctorFirstName, d.lastName as doctorLastName, d.title as doctorTitle, d.email as doctorEmail, " +
"v.id as id, v.dateFrom as dateFrom, v.dateTo as dateTo, v.status as status, md.id as medicalServicesId, md.service as medicalServicesService, md.price as medicalServicesPrice \n" +
"from Visit v \n" +
"left outer join Doctor d on v.doctor.id=d.id \n" +
"left outer join v.medicalServices vms on vms.id=v.id \n" +
"left outer join MedicalService md on md.id=vms.id \n" +
"where d.id= :doctorId and v.status= :status and v.dateFrom>= :dateFrom and v.dateTo<= :dateTo")
List<VisitInfoWithPatientAndMedServices3joins> getAllVisitInfoWithPatientAndMedicalServicesJpqlQuery
(#Param("doctorId") Long doctorId, #Param("status") VisitStatus status, #Param("dateFrom") LocalDateTime dateFrom, #Param("dateTo") LocalDateTime dateTo, Pageable pageable);
And here is translated SQL from JPQL.
select doctor1_.id as col_0_0_, doctor1_.firstName as col_1_0_, doctor1_.lastName as col_2_0_, doctor1_.title as col_3_0_, doctor1_.email as col_4_0_, visit0_.id as col_5_0_, visit0_.dateFrom as col_6_0_, visit0_.dateTo as col_7_0_, visit0_.status as col_8_0_, medicalser4_.id as col_9_0_, medicalser4_.service as col_10_0_, medicalser4_.price as col_11_0_
from Visit visit0_
left outer join
(visit_medical_services medicalser2_ left outer join MedicalService medicalser3_ on medicalser2_.visit_id=medicalser3_.id) on visit0_.id=medicalser2_.medical_services_id
and (medicalser3_.id=visit0_.id)
left outer join Doctor doctor1_ on (visit0_.doctor_id=doctor1_.id)
left outer join MedicalService medicalser4_ on (medicalser4_.id=medicalser3_.id) where doctor1_.id=? and visit0_.status=? and visit0_.dateFrom>=? and visit0_.dateTo<=? limit ?
And the problem here is, that it's returning columns from #ManyToMany relations (visit_medical_services) but ONLY FOR THE FIRST OBJECT. Json response from Postman below:
[
{
"id": 1,
"status": "PAID",
"dateFrom": "2019-04-10T08:00:00",
"dateTo": "2019-04-10T08:30:00",
"medicalServicesId": 1,
"medicalServicesService": "Visit",
"medicalServicesPrice": 100.0,
"doctorLastName": "James",
"doctorFirstName": "Alex",
"doctorEmail": "james#gmail.com",
"doctorId": 1,
"doctorTitle": "dr n. md."
},
{
"id": 2,
"status": "PAID",
"dateFrom": "2019-04-10T09:00:00",
"dateTo": "2019-04-10T09:30:00",
"medicalServicesId": null,
"medicalServicesService": null,
"medicalServicesPrice": null,
"doctorLastName": "James",
"doctorFirstName": "Alex",
"doctorEmail": "james#gmail.com",
"doctorId": 1,
"doctorTitle": "dr n. md."
}
]
I tried to use translated SQL query using JPQL in Workbench, because I'm using MySQL database and the result is the same - first object is correct, and the rest have null values inside mapped #ManyToMany colums.
Here are my Entity classes if it would this issue a little bit easier:
public class Visit {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
LocalDateTime dateFrom;
LocalDateTime dateTo;
#Enumerated(EnumType.STRING)
VisitStatus status;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "doctor_id", referencedColumnName = "id", nullable = true)
#JsonManagedReference
Doctor doctor;
#ManyToOne(fetch = FetchType.LAZY)
#JsonManagedReference
#JoinColumn(name = "patient_id", referencedColumnName = "id", nullable = true)
Patient patient;
#ManyToMany
#JsonManagedReference
#JoinTable(
name = "visit_diseases",
joinColumns = #JoinColumn(
name = "disease_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(
name = "visit_id", referencedColumnName = "id"))
List<Disease> diseases;
#ManyToMany
#JsonManagedReference
#JoinTable(
name = "visit_medical_services",
joinColumns = #JoinColumn(
name = "medical_services_id"),
inverseJoinColumns = #JoinColumn(
name = "visit_id"))
Set<MedicalService> medicalServices;
String mainSymptoms;
String treatment;
String allergy;
String addiction;
String comment;
}
And Medical Services:
public class MedicalService {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
Long id;
String service;
Float price;
#ManyToMany(mappedBy = "medicalServices", fetch = FetchType.EAGER)
private Set<Visit> visits;
public MedicalService(Long id, String service, float price) {
this.id = id;
this.service = service;
this.price = price;
}
}
Can someone take a look please and explain to me what is not working correctly here? What i want to achive is to make JPQL generate THE SAME SQL query. It is even possible? Please help me...
You have a few errors in your query. Use the following instead
#Query("select d.id as doctorId, d.firstName as doctorFirstName, d.lastName as doctorLastName, d.title as doctorTitle, d.email as doctorEmail, " +
"v.id as id, v.dateFrom as dateFrom, v.dateTo as dateTo, v.status as status, md.id as medicalServicesId, md.service as medicalServicesService, md.price as medicalServicesPrice \n" +
"from Visit v \n" +
"left outer join v.doctor d \n" +
"left outer join v.medicalServices md \n"
"where d.id= :doctorId and v.status= :status and v.dateFrom>= :dateFrom and v.dateTo<= :dateTo")
List<VisitInfoWithPatientAndMedServices3joins> getAllVisitInfoWithPatientAndMedicalServicesJpqlQuery
(#Param("doctorId") Long doctorId, #Param("status") VisitStatus status, #Param("dateFrom") LocalDateTime dateFrom, #Param("dateTo") LocalDateTime dateTo, Pageable pageable);
Related
I do not know how to implement the query below in jpa, so please help.
select t.*
from team t
join team_memeber tm
on t.team_id = tm.team_id and tm.member_id = ?
The related entities are as follows.
public class Team {
#Id
int teamId;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "teamId")
Set<TeamMember> members = new HashSet<>();
}
public class TeamMember {
#Id
int id;
int teamId;
int memberId;
}
I have two tables:
DIDPool
DIDRange
A pool can have many ranges.
The range table doesn't have a parent id (e.g. no foreign key back to the pool) as the range table is used by a number of other entities.
As such I've setup the join so that it uses an intermediate join table tbldidpooldidrange.
#Entity
#Table(name = "tbldidpool")
class DIDPool
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(updatable = false, nullable = false)
private long id;
#OneToMany
#JoinTable(name = "tbldidpooldidrange", joinColumns = #JoinColumn(name = "did_pool_id")
, inverseJoinColumns = #JoinColumn(name = "did_range_id"))
private ArrayList<DIDRange> didRanges = new ArrayList<DIDRange>();
}
#Entity
#Table(name = "tbldidrange")
public class DIDRange
{#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(updatable = false, nullable = false)
private long id;
#NotEmpty
#NotNull
#AttributeOverride(name = "phoneNumber", column = #Column(name = "start"))
private PhoneNumber start = new PhoneNumber();
}
The jpa correctly generates the intermediate join table.
My problem is in creating a jql expression that joins the two tables.
// Finds the pool that contains the given range or part range.
#SuppressWarnings("unchecked")
public List<DIDPool> findOverlapping(DIDRange didRange)
{
EntityManager em = EntityManagerProvider.getEntityManager();
Query query = em.createQuery("SELECT e FROM DIDPool e "
+ "join DIDRange r "
+ "where r.start.phoneNumber=:startNo"
+ " ) "
);
query.setParameter("startNo", didRange.getStartOfDIDRange().toCompactString());
return (List<DIDPool>) query.getResultList();
}
My understanding of jql is that you don't specify the join 'on' clause as jpa knows how to generate it.
However this is the sql the above clause generates:
SELECT t1.ID,
FROM tbldidrange t0, tbldidpool t1 WHERE t0.start = ?
bind => [61383208100]
As you can see there is no 'on' clause (and the where doesn't join the tables) and the intermediate join table isn't even mentioned.
What am I doing wrong here?
I've been the reading the jpa documentation and its really not very helpful.
Your query should be
Query query = em.createQuery("SELECT e FROM DIDPool e "
+ "join e.didRanges r "
+ "where r.start.phoneNumber=:startNo"
+ " ) "
);
You have to use the ToMany attribute in the JOIN.
I have 2 entities -
#Table(name = "MM_MONITORING_CARD")
#Entity(name = "mm_MonitoringCard")
public class MonitoringCard extends StandardEntity {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "CLIENT_ID")
private Counterparty client;
#Column(name = "START_DATE")
private LocalDate startDate;
#Column(name = "END_DATE")
private LocalDate endDate;
...otherColumn
}
and
#DiscriminatorColumn(name = "TYPE", discriminatorType = DiscriminatorType.STRING)
#Table(name = "MM_COUNTERPARTY")
#Entity(name = "mm_Counterparty")
#DiscriminatorValue("COUNTERPARTY")
#NamePattern("%s|name")
public class Counterparty extends StandardEntity {
#Column(name = "TYPE", insertable=false, updateable=false)
private String type;
...otherColumn
I need get all examples monitoringCard with condition - 1) between start date and end date 2) with Counterparty type = 'someType'
I do method -
List<MonitoringCard> monitoringCardList = dataManager.load(MonitoringCard.class)
.query("select distinct cm from mm_MonitoringCard m join mm_Counterparty cm where (m.current_date between cm.startDate and cm.endDate) and cm.type = :type")
.parameter("type", "someType")
.list();
but i get error, how can i make a correct request?
thanks
Your query is not correct. It should use a join like this:
select distinct m.Counterparty from mm_MonitoringCard m
where (m.current_date between m.Counterparty.startDate and m.Counterparty.endDate)
and m.Counterparty.type = :type
In JPA you don't need to do an explicit join when navigating on a ToOne relationship.
I have problem with my JPQl. this is a one to many relationship with TrainRoute and TrainRouteStation. I'm trying to create a inner join and fetch the data. native SQL query working when I used mysql workbeanch and I'm trying convert it to JPQL. also, I was trying to fix from 2 days.
Error : Object comparisons can only be used with OneToOneMappings. Other mapping comparisons must be done through query keys or direct attribute level comparisons.
Class: TrainRoute
#Basic(optional = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
#Column(name = "train_route_id", unique = true, nullable = false)
public Long getTrainRouteId() {
return this.trainRouteId;
}
public void setTrainRouteId(Long trainRouteId) {
this.trainRouteId = trainRouteId;
}
#OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER, mappedBy = "trainRoute")
public List<TrainRouteStationData> getTrainRouteStations() {
return this.trainRouteStations;
}
public void setTrainRouteStations(List<TrainRouteStationData> trainRouteStations) {
this.trainRouteStations = trainRouteStations;
}
Class: TrainRouteStation
#Basic(optional = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
#Column(name = "train_route_station_id", unique = true, nullable = false)
public Long getTrainRouteStationId() {
return this.trainRouteStationId;
}
public void setTrainRouteStationId(Long trainRouteStationId) {
this.trainRouteStationId = trainRouteStationId;
}
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "train_route_id", nullable = false)
public TrainRouteData getTrainRoute() {
return this.trainRoute;
}
JPQL :"SELECT s FROM TrainRouteData t inner join TrainRouteStationData s ON t.trainRouteId=s.trainRoute where s.stationSeqN >=1 AND s.stationSeqN <=3 AND t.trainRouteDescX='Test1-Test2' order by s.stationSeqN asc"
Native SQL : SELECT train_route_station.* FROM train_route inner join train_route_station ON train_route.train_route_id=train_route_station.train_route_id where train_route_station.station_seq_n >= 1 AND train_route_station.station_seq_n <= 3 AND train_route.train_route_desc_x='Test1-Test2' order by train_route_station.station_seq_n asc
And it throw an error:
Exception Description: Object comparisons can only be used with OneToOneMappings. Other mapping comparisons must be done through query keys or direct attribute level comparisons.
Mapping: [org.eclipse.persistence.mappings.DirectToFieldMapping[trainRouteId-->train_route.train_route_id]]
Expression: [
Query Key trainRouteId
How can I change that query?
That's not how joins work in JPQL.
The correct query is
select s from TrainRouteData t inner join t.trainRouteStations s
where s.stationSeqN >= 1
and s.stationSeqN <= 3
and t.trainRouteDescX = 'Test1-Test2'
order by s.stationSeqN asc
I'm trying to convert a native SQL query into a JPQL query. Presented first are the JPA entities and the repository interface:
LearnerActivity
#Entity
#Table(name = "learner_activity")
public class LearnerActivity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToMany
#JoinTable(name = "learner_activity_unit",
joinColumns = #JoinColumn(name="learner_activitys_id", referencedColumnName="ID"),
inverseJoinColumns = #JoinColumn(name="units_id", referencedColumnName="ID"))
private Set<Unit> units = new HashSet<>();
#ManyToMany
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JoinTable(name = "learner_activity_performance_criteria",
joinColumns = #JoinColumn(name="learner_activitys_id", referencedColumnName="ID"),
inverseJoinColumns = #JoinColumn(name="performance_criterias_id", referencedColumnName="ID"))
private Set<PerformanceCriteria> performanceCriterias = new HashSet<>();
}
LearnerJobOnSiteChecklistSectionItem
#Entity
#Table(name = "learner_job_on_site_checklist_section_item")
public class LearnerJobOnSiteChecklistSectionItem implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn(name = "learner_activity_id")
private LearnerActivity learnerActivity;
}
LearnerPortfolioPerformanceCriteriaAchievement
#Entity
#Table(name = "learner_portfolio_performance_criteria_achievement")
public class LearnerPortfolioPerformanceCriteriaAchievement implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn(name = "performance_criteria_id")
private PerformanceCriteria performanceCriteria;
#ManyToMany
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JoinTable(name = "learner_portfolio_performance_criteria_achievement_learner_job_on_site_checklist_section_item",
joinColumns = #JoinColumn(name="learner_portfolio_performance_criteria_achievements_id", referencedColumnName="ID"),
inverseJoinColumns = #JoinColumn(name="learner_job_on_site_checklist_section_items_id", referencedColumnName="ID"))
private Set<LearnerJobOnSiteChecklistSectionItem> learnerJobOnSiteChecklistSectionItems = new HashSet<>();
}
I'm looking to get all LearnerPortfolioPerformanceCriteriaAchievements which have the same PerformanceCriteria as a given LearnerJobOnSiteChecklistSectionItem's Activity.
LearnerPortfolioPerformanceCriteriaAchievementRepository
public interface LearnerPortfolioPerformanceCriteriaAchievementRepository extends JpaRepository<LearnerPortfolioPerformanceCriteriaAchievement,Long> {
#Query("select distinct lppca from LearnerJobOnSiteChecklistSectionItem si inner join si.learnerActivity la inner join LearnerPortfolioPerformanceCriteriaAchievement lppca where lppca.performanceCriteria member of la.performanceCriterias and si.id =:sectionItemId")
public List<LearnerPortfolioPerformanceCriteriaAchievement> findForSectionItem(#Param("sectionItemId") Long sectionItemId);
}
Native SQL
SELECT
a.id
FROM
learner_job_on_site_checklist_section_item AS i
JOIN
learner_activity_performance_criteria AS c
ON
c.learned_activitys_id = i.learned_activitys_id
INNER JOIN
learner_portfolio_performance_criteria_achievement AS a
ON
a.performance_criteria_id = c.performance_criterias_id
where i.id = 2
Understanding that I can replace my SQL ON with WHERE clauses, my best JPQL attempt so far is this:
SELECT
DISTINCT lppca
FROM
LearnerJobOnSiteChecklistSectionItem si
INNER JOIN
si.learnerActivity la
INNER JOIN
LearnerPortfolioPerformanceCriteriaAchievement lppca
WHERE
lppca.performanceCriteria MEMBER OF la.performanceCriterias
AND si.id =:sectionItemId
However, running this JPQL throws the following exception:
Caused by: java.lang.IllegalStateException: No data type for node: org.hibernate.hql.internal.ast.tree.IdentNode
\-[IDENT] IdentNode: 'lppca' {originalText=lppca}
The following query will work:
SELECT
a
FROM
LearnerPortfolioPerformanceCriteriaAchievement a
, LearnerActivity v
, LearnerJobOnSiteChecklistSectionItem i
WHERE
i.id = :sectionItemId
AND i.learnerActivity = v
AND a.performanceCriteria MEMBER OF v.performanceCriterias
AND i MEMBER OF a.learnerJobOnSiteChecklistSectionItems
Have created a sample application to test the query (I have used shorter and simpler class names to make it easy to read).