I have the following model:
public class BaseModel {
List<DataA> lazyCollectionA;
List<DataB> lazyCollectionB;
}
public class DataA {
OtherEntity otherEntity;
}
public class OtherEntity {
List<DataC> lazyCollectionC;
}
When I visit a particular page I need to use all this data. This is creating a performance select n+1 problem.
I already partly solved the issue by eagerly fetching the collections using:
List<BaseModel> result = entityManager.createQuery(
"select m from BaseModel m " +
"left join fetch m.lazyCollectionA " +
"where m.id in (:ids) ", BaseModel.class)
.setParameter("ids", ids)
.getResultList();
result = entityManager.createQuery(
"select m from BaseModel m " +
"left join fetch m.lazyCollectionB " +
"where m.id in (:ids) ", BaseModel.class)
.setParameter("ids", ids)
.getResultList();
Note that I had to perform 2 queries instead of only 1 because otherwise I would get a MultipleBagFetchException.
However, I'm having problems eagerly loading lazyCollectionA.otherEntity.lazyCollectionC. I tried several variations of the query to try to eagerly fetch the results, but when otherEntity.lazyCollectionC is accessed, the select n+1 problem keeps surfacing.
I think this should work, but unfortunately it is not:
entityManager.createQuery(
"select a from BaseModel m " +
"left join m.lazyCollectionA a " +
"left join fetch a.otherEntity o " +
"left join fetch o.lazyCollectionC " +
"where m.id in (:ids) ", BaseModel.class)
.setParameter("ids", ids)
.getResultList();
Do you have any ideas why this is not working?
Also, I don't fully understand how my first 2 queries to load lazyCollectionA and lazyCollectionB are working. I mean, since they are loaded at different times, I would expect that only the last query would have the loaded instances. Is it because hibernate is caching the results and therefore it does not need to query the database again?
Thanks for any help you can provide!
I assume that all connections between your models are #OneToMany. In this case you could try simething like this:
#Autowired
private EntityManager em;
#Transactional
public List<BaseModel> getAllByThreeQueries() {
List<Long> ids = Arrays.asList(1L);
List<BaseModel> first = em.createQuery(
"select distinct m from BaseModel m " +
"left join fetch m.lazyCollectionB " +
"where m.id in (:ids) ", BaseModel.class)
.setParameter("ids", ids)
.getResultList();
List<BaseModel> second = em.createQuery(
"select distinct m from BaseModel m " +
"left join fetch m.lazyCollectionA a " +
"left join fetch a.otherEntity o " +
"where m in (:models) ", BaseModel.class)
.setParameter("models", first)
.getResultList();
em.createQuery("select distinct a from BaseModel m " +
"left join m.lazyCollectionA a " +
"left join fetch a.otherEntity o " +
"left join fetch o.lazyCollectionC " +
"where m in (:models) ", DataA.class)
.setParameter("models", second)
.getResultList();
return second;
}
Full code
Do you have any ideas why this is not working?
entityManager.createQuery(
"select a from BaseModel m " +
"left join m.lazyCollectionA a " +
"left join fetch a.otherEntity o " +
"left join fetch o.lazyCollectionC " +
"where m.id in (:ids) ", BaseModel.class)
.setParameter("ids", ids)
.getResultList();
Because you get a MultipleBagFetchException in this case. You need to do one more request.
Related
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
I'm using JpaRepository and I'm creating a #Query in my repository:
this is my query:
#Query( "SELECT SUM(p.prima) as prima, p.producto as producto, p.tipoProducto as tipoProducto, p.compania as compania, p.cliente as cliente, p.vendedor as vendedor " +
"FROM Poliza p " +
"JOIN Producto pr ON p.producto=pr " +
"JOIN TipoProducto tp ON p.tipoProducto=tp " +
"JOIN Compania c ON p.compania=c " +
"JOIN Cliente cl ON p.cliente=cl " +
"LEFT JOIN Vendedor v ON p.vendedor=v " +
"WHERE p.comienzo >=?1 " +
"AND p.comienzo <= ?2 " +
"GROUP BY p.producto")
and I realize that I only get the rows where "Vendedor" is present.
I used the spring.jpa.show-sql=true property to check what was going on and I realize that the query is creating an inner join for each property in the SELECT STATEMENT
inner join producto producto1_ on (poliza0_.producto=producto1_.id)
inner join tipo_producto tipoproduc2_ on (poliza0_.tipo_producto=tipoproduc2_.id)
inner join compania compania3_ on (poliza0_.compania=compania3_.id)
inner join cliente cliente4_ on (poliza0_.cliente=cliente4_.id)
inner join vendedor vendedor5_ on (poliza0_.vendedor=vendedor5_.id)
join producto producto6_ on poliza0_.producto=producto6_.id
join tipo_producto tipoproduc7_ on poliza0_.tipo_producto=tipoproduc7_.id
join compania compania8_ on poliza0_.compania=compania8_.id
join cliente cliente9_ on poliza0_.cliente=cliente9_.id
left join vendedor vendedor10_ on poliza0_.vendedor=vendedor10_.id
As you can see in the first part I have an inner join from Vendedor which makes the query wrong.
How should I create my query to get the expected result?
if understand clearly
#Query( "SELECT SUM(p.prima) as prima, p.producto as producto, p.tipoProducto as tipoProducto, p.compania as compania, p.cliente as cliente, p.vendedor as vendedor " +
"FROM Poliza p " +
"JOIN Producto pr ON p.producto=pr " +
"JOIN TipoProducto tp ON p.tipoProducto=tp " +
"JOIN Compania c ON p.compania=c " +
"JOIN Cliente cl ON p.cliente=cl " +
"LEFT JOIN Vendedor v ON p.vendedor=v " +
"WHERE p.comienzo >=?1 " +
"AND p.comienzo <= ?2 " +
"GROUP BY p.producto")
i think you can using v instead p.vendedor as vendedor in select field.
If I read correctly, you are defining your own query. So, you can edit the JOIN in your query and write LEFT JOIN instead.
Am I right on your intentions?
Currently I have such piece of code, which doesn't work, since I have to add schema name before each table in a query(like DEV.DASHBOARDS_METADATA):
public interface DashboardMetadataDao extends CrudRepository<DashboardMetadata, Integer> {
#Query("SELECT D FROM DASHBOARDS_METADATA D " +
"INNER JOIN FAC_DASHBOARDS_LINK DL ON D.ID = DL.DASHBOARD_ID " +
"INNER JOIN FIRMS F ON DL.FAC_ID = F.FAC_UNIT_ID " +
"INNER JOIN USERS U ON U.FIRM_ID = F.FIRM_ID WHERE LOWER(U.USERID) = LOWER(:userid)")
public Set<DashboardMetadata> findByUserId(#Param("userid") String userId);
}
The problem is that schema name differs from database to database (DEV/QA/PROD). Normally I use component's method which prepend schema's name to each table during query generation. How can do this using annotations?
Thanks!
Hibernate has a variable that can be used in native queries to get schema name called: {h-schema}
https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/chapters/query/native/Native.html#sql-global-catalog-schema You can just put {h-schema}table in your query. I think you have to convert it to a native query.
Below Code Snippet worked for me
#Query(
value = "select *\n" +
"from {h-schema}TABLE-A r left join {h-schema}TABLE-B p on r.ID=p.ID\n" +
"left join {h-schema}TABLE-C pe on p.ID=pe.ID\n" +
"left join {h-schema}TABLE-D e on pe.ENT_ID=e.ENT_ID\n" +
"where r.role_type='Provisioning'",
nativeQuery = true)
Currently I have such piece of code, which doesn't work, since I have to add schema name before each table in a query(like DEV.DASHBOARDS_METADATA):
public interface DashboardMetadataDao extends CrudRepository<DashboardMetadata, Integer> {
#Query("SELECT D FROM DASHBOARDS_METADATA D " +
"INNER JOIN FAC_DASHBOARDS_LINK DL ON D.ID = DL.DASHBOARD_ID " +
"INNER JOIN FIRMS F ON DL.FAC_ID = F.FAC_UNIT_ID " +
"INNER JOIN USERS U ON U.FIRM_ID = F.FIRM_ID WHERE LOWER(U.USERID) = LOWER(:userid)")
public Set<DashboardMetadata> findByUserId(#Param("userid") String userId);
}
The problem is that schema name differs from database to database (DEV/QA/PROD). Normally I use component's method which prepend schema's name to each table during query generation. How can do this using annotations?
Thanks!
Hibernate has a variable that can be used in native queries to get schema name called: {h-schema}
https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/chapters/query/native/Native.html#sql-global-catalog-schema You can just put {h-schema}table in your query. I think you have to convert it to a native query.
Below Code Snippet worked for me
#Query(
value = "select *\n" +
"from {h-schema}TABLE-A r left join {h-schema}TABLE-B p on r.ID=p.ID\n" +
"left join {h-schema}TABLE-C pe on p.ID=pe.ID\n" +
"left join {h-schema}TABLE-D e on pe.ENT_ID=e.ENT_ID\n" +
"where r.role_type='Provisioning'",
nativeQuery = true)
I have the following query with all of these join/fetches:
public interface InitiativeRepository extends JpaRepository<Initiative, Long> {
#Query("select distinct i from Initiative i " +
"left join fetch i.theme t " +
"left join fetch t.themeQuestions tq " +
"left join fetch tq.initiativeProfileQuestion ipq " +
"left join fetch ipq.answers " +
"left join fetch ipq.answerLogs al " +
"where al.revision = i.revision " +
"order by ipq.question asc")
public List<Initiative> getThemeAndQuestionsAndAnswerLogs();
}
Because I'm doing so many joins, obviously hibernate is fetching all attributes of each object. For example, left join fetch i.theme t fetches all attributes of theme. What if I only want to fetch the theme name and themeQuestions attribute within the theme object (I don't want any unnecessary ones)?
I'm not sure if this is doable through only an annotated query. Any ideas would be appreciated.