How to properly Select DTO projections using JPA Join tables? - java

I have one entity and a one to one relationship. Now i'm looking for the best way to retrieve specific fields from both tables using a projection.
In my first attempt i've tried:
"SELECT NEW br.com.domain.order.Order( " +
"o.id," +
"o.description," +
"p.payment) " +
"FROM Order o
Using this way i have a single constructor inside Order entity, to retrieve only these 3 fields. But now i have N+1 queries.
The second option is the follow:
"SELECT NEW br.com.domain.order.OrderDTO( " +
"o.id," +
"o.description," +
"p.status,
p.locationId,
p.value) " +
"FROM Order o JOIN o.payment p
This works great but i have to create a DTO class.
How can i do something like this:
"SELECT NEW br.com.domain.order.Order( " +
"o.id," +
"o.description," +
"p.status,"+
"p.locationId,"+
"p.value)"
It's necessary to create a constructor inside both entities to load all properties?

Related

incorrect #query in spring boot

I am trying to use this query in springboot so I can display the results in a webpage. I know that this query works because I tested it in postgresql and it gave me the right results.
But JPA is telling me that the '(' after the first FROM is an unexpected token and the query was therefore viewed as invalid.
This is my query:
#Query(
"SELECT com.example.imse22.model.TrvlA_Cust_Dto(books_query.name, count(travelA_query.customer_id)) " +
"FROM (SELECT DISTINCT customer_servant.employee_id, books.customer_id FROM customer_servant " +
"INNER JOIN books ON customer_servant.employee_id = books.customer_servant_id) AS travelA_query " +
"INNER JOIN " +
"(SELECT travel_agency.id, travel_agency.name, employee.employee_id FROM travel_agency " +
"INNER JOIN employee ON travel_agency.id = employee.travel_agency_id) AS books_query " +
"ON travelA_query.employee_id = books_query.employee_id " +
"GROUOP BY travelA_query.name")
can somebody help me out how I could rewrite the query so that JPA approves it?
Your query is native so you should declare it in that way:
#Query(
value = "SELECT com.example.imse22.model.TrvlA_Cust_Dto(books_query.name, count(travelA_query.customer_id)) " +
"FROM (SELECT DISTINCT customer_servant.employee_id, books.customer_id FROM customer_servant " +
"INNER JOIN books ON customer_servant.employee_id = books.customer_servant_id) AS travelA_query " +
"INNER JOIN " +
"(SELECT travel_agency.id, travel_agency.name, employee.employee_id FROM travel_agency " +
"INNER JOIN employee ON travel_agency.id = employee.travel_agency_id) AS books_query " +
"ON travelA_query.employee_id = books_query.employee_id " +
"GROUOP BY travelA_query.name", nativeQuery = true)
link point 2.2: https://www.baeldung.com/spring-data-jpa-query
Ok so this is how I solved it:
I changed my query into a native one just like #notAPPP pointed out and then I only had to add an alias for the LEFT JOIN (also I changed INNER JOIN to LEFT JOIN).
here the code example:
#Query(value = "SELECT combined.name as name, count(combined.customer_id) as id " +
"FROM (" +
"(SELECT travel_agency.name, travel_agency.id, employee.employee_id " +
"FROM travel_agency INNER JOIN employee ON travel_agency.id = employee.travel_agency_id) as trvlEmp " +
"LEFT JOIN " +
"(SELECT books.customer_id, books.customer_servant_id, customer_servant.employee_id " +
"FROM books INNER JOIN customer_servant ON books.customer_servant_id = customer_servant.employee_id) as custBooks " +
"ON trvlEmp.employee_id = custBooks.employee_id) as combined " + // this "AS combined" got added
"GROUP BY combined.name", nativeQuery = true)
This makes sense, because after a FROM clause one should wirte the name of a table or a result table (e.g. from two joined queries like in my case). As I didnt specify an alias for the LEFT JOIN of my two subqueries, JPA obviously didnt know how to handle the result of those subqueries. Therefore always name your subqueries if they are not used in a WHERE clause, but rather with a FROM clause, like in my case. E.g. the name I gave my LEFT JOIN is "combined" as seen in the code example above.
Also I changed my INNER JOIN to a LEFT JOIN to get the value 0 of the elements that have 0 counts of what I wanted to count in the table.
If you want to know how to handle the result which such a query returns follow this link.thorben-janssen.com/spring-data-jpa-dto-native-queries

Spring Data JPA: Using multiple entity in SpEL notation?

I used SpEL for generic entity name in my Spring Data JPA repositories. This time, I need to join 2 tables via JPQL and for this purpose, I am wondering if I can use multiple entity by passing to the repository and then using it in my query as SpEL 's ${#entityName}. So, is it possible?
DiningRepository:
#Query("select d as dining, dg as diningGroup, s as site " +
"from Dining d " +
" join DiningGroup dg on d.groupUuid = dg.uuid " +
" join Site s on dg.siteUuid = s.uuid " +
"where d.uuid = :uuid")
Optional<GroupAndSiteProjection> findByUuid(#Param("uuid") UUID uuid);
Here is projection where I collect Dining, DiningGroup and ``Site` result:
public interface GroupAndSiteProjection {
Dining getDining();
DiningGroup getDiningGroup();
Site getSite();
}
The method is in DiningRepository and ${#entityName} gets Dining entity. But I want to use also DiningGroup by passing it to the repository as generic e.g. DiningRepository<Dining, DiningGroup>... Is it possible?

Spring JPA filter optional criteria in Query method

I want to filter out data from a database with few criteria (let's assume it is 8).
And below query method do this in a good way. But in fact, this criterias passed to the query method can be null (it means that should not be included to select query).
How I should handle this situation?
I really don't want to make n-methods to handle each case - it is not a good way.
#Query("SELECT NEW api.model.GeneralAnnouncementInfo(" +
"an.id, an.title, po.price, SUBSTRING(an.description, 1, 100), an.provider, an.creationDate, an.url, l.lessorType, concat(loc.city, ' ', loc.district)) " +
"FROM Announcement as an " +
"LEFT JOIN an.priceOffer as po " +
"LEFT JOIN an.lessor as l " +
"LEFT JOIN an.location as loc " +
"LEFT JOIN an.propertyData as pd " +
"WHERE l.lessorType = (:lessor) " +
"AND pd.roomNumber = (:rooms) " +
"AND pd.bathroomNumber = (:baths) " +
"AND pd.parkingAvailability = (:parking) " +
"AND pd.isSmokingAllowed = (:smokers) " +
"AND pd.isPetFriendly = (:pets) " +
"AND pd.area = (:realPrice) " +
"AND po.price = (:area) ")
Page<GeneralAnnouncementInfo> getAnnouncementsBySearchCriteria(Pageable pageable,
String lessor,
String rooms,
String baths,
String parking,
String smokers,
String pets,
String realPrice,
String area
);
I would recommend switching to the JPA Criteria API. It will give you the extra flexibility you are seeking (and which JPQL seems to be maxing out for your case). You can build your queries programmatically without any limitations and the best thing is that they get compiled; which means that no typos will survive (which are a nightmare to track in JPQL queries). Additionally you may want to use JPA metamodel classes; which add more robustness to your queries. At the end, your repository method would look something like this:
private EntityManager em;
private Page<GeneralAnnouncementInfo> getAnnouncementsBySearchCriteria(QueryParameters qParams) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<SampleEntity> criteria = cb.createQuery(GeneralAnnouncementInfo.class);
Root<GeneralAnnouncementInfo> root = criteria.from(GeneralAnnouncementInfo.class);
// Programmatically build query details (conditions, joins, aggregations, translation, etc)
// ...
// ...
// ...
return em.createQuery(criteria).getResultList();
}

JPQL Constructor execution takes long time

I am using JPQL constructor in my code. It involves large results, 5024 data retrieved and objects created when executing this query. It is taking around 2 minutes to complete its execution. There are multiple tables involved to execute this query. I could not go for cache since the DB data will update daily. Is there any way to optimize the execution speed?
My SQL query formation is like this,
String sql = "SELECT DISTINCT "
+ "new com.abc.dc.entity.market.LaptopData(vd.segment.id, vd.segment.segmentGroup, "
+ "vd.segment.segmentName, vd.segment.category, "
+ "vd.product.make, vd.product.model, vd.product.modelYear) "
+ "FROM LaptopData vd, Profile p "
+ "WHERE vd.profile.profileCode = :profileCode "
+ "AND vd.pk.profileId = p.id "
+ "Order By vd.segment.segmentGroup, vd.segment.segmentName, vd.product.make, vd.product.model,"
+ " vd.product.modelYear asc";
Query query = em.createQuery(sql);
query.setParameter("profileCode", profileCode);
#SuppressWarnings("unchecked")
List<LaptopData> results = query.getResultList();
em.clear();
return results;
Well, one option for you is to create separate table which would contain result of this query for all entities and update it daily (every night) and then run your query on this new table, like this:
"SELECT DISTINCT"
+ "new com.abc.dc.entity.market.LaptopData(m.id, m.segmentGroup, "
+ "m.segmentName, m.category, "
+ "m.make, m.model, m.modelYear) "
+ "FROM someNewTable m"
+ "WHERE m.profileCode = :profileCode ";
This way you don't have to do join and ordering every time you execute your query, but only once a day when you generate new table. Ofcourse with this approach to see data updates you'll have to wait until new table is recreated.
Also, you can create indexes on fields you are using in where clause.

Formatting hql query

#My doubt is the partId in Product table can be null ,so if the partId is null I’m not able to see the product. If my Product Table has 11 entries and 2 entries have partId as null , Iam able to see only 9 entries
String hql = "from " + Product.class.getName() + " bs, "
+ Part.class.getName() + " dm, "
+ Manufacturer.class.getName() + " m where "
+ " m.id = bs.manufacturerId and dm.id = bs.partId ";
========================================
The ouput has to be like this
productName | PartName | Manufactuer Name
You need to do left joins instead of inner joins. But this is only possible if your entities are associated together instead of containing each other's IDs.
As is, it's plain impossible with HQL.
Given your query, you probably should have a ManyToOne between Product and Manufacturer, and a ManyToOne between Product and Part.
Also, your query would be much more readable if you didn't concatenate class names and if you used proper alias names:
String hql = "from Product product, Part part, Manufacturer manufacturer"
+ " where manufacturer.id = product.manufacturerId"
+ " and part.id = product.partId";
Once the associations exist, the query should just be
String hql = "select product.name, part.name, manufacturer.name"
+ " from Product product"
+ " left join product.part part"
+ " left join product.manufacturer manufacturer";

Categories

Resources