Spring Roo doesn't add FetchType.LAZY for fields in .aj files, Should I do it manually? - spring

These are a couple of question, should Spring Roo through reverse engineering adding FetchType.LAZY for Set fields in .aj files or should I do it manually?
If FetchType.LAZY is not present at .aj files, I could make it through the queries " SELECT st1 FROM parentTable t1 JOIN FETCH t1.childTable st1" into the select, right ?
The point here is that I can add FetchType.LAZY to the files manually (Refactor .aj file > Push In..) and then on .java file (high risk if I want Roo to keep the control on my domain classes).
About to make it through queries, I can't do this because I'm getting:
> query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=ii,role=...domain.Industry.i18nIndustries,
tableName=..I18nIndustry,
tableAlias=i18nindust3_,
origin=..Industry industry2_,
columns={industry2_.IdIndustry ,className=..domain.I18nIndustry}}]
[select u.idUserName, u.isActiveInd, u.employeeNbr, u.name, c.name as CompanyName, ii.name as IndustryName from Users u JOIN u.idCompany c JOIN c.idIndustry i JOIN FETCH i.i18nIndustries ii WHERE u.idUserName = :username ]
Here the .aj files generated by Roo:
privileged aspect Users_Roo_DbManaged {
...
#ManyToOne
#JoinColumn(name = "IdCompany", referencedColumnName = "IdCompany", nullable = false)
private Company Users.idCompany; ...
..
privileged aspect Company_Roo_DbManaged {
...
#ManyToOne
#JoinColumn(name = "IdCity", referencedColumnName = "IdCity", nullable = false)
private City Company.idCity;
#ManyToOne
#JoinColumn(name = "IdIndustry", referencedColumnName = "IdIndustry", nullable = false)
private Industry Company.idIndustry; ..
..
privileged aspect Industry_Roo_DbManaged { ..
#OneToMany(mappedBy = "idIndustry")
private Set<I18nIndustry> Industry.i18nIndustries; ...
Can you please give me a clue, about what is happening here ?
Spring MVC 3.2,
Roo 1.2.4,
MSSql database
Thanks folks!
--JR

About
These are a couple of question, should Spring Roo through reverse engineering adding FetchType.LAZY for Set fields in .aj files or should I do it manually?
This is not needed because this is the default for this kind of relationship (see https://stackoverflow.com/a/13765381/2295657).
If FetchType.LAZY is not present at .aj files, I could make it through the queries " SELECT st1 FROM parentTable t1 JOIN FETCH t1.childTable st1" into the select, right ?
This makes a eager loading of the relationship data. I think you are in a mistake about the meaning of LAZY and EAGER modes:
EAGER: Requires to load value when instance is loaded into memory (default for basic and no-collections-relationship properties)
LAZY: when instance is loaded a proxy is stored in property and, when anything tries to get data, loads it form DB (default for collections-relationship properties)
On the other hand, to load a LAZY property requires a DB Connection alive when anything tries to get the data, so, you must execute this code inside a no-static, #Transactional annotated, method (read #Transactional JavaDoc for more info).
I advice you to make a look in the JPA specification. In my experience, I so useful.

I found whats happening, This is your HQL
[select u.idUserName, u.isActiveInd, u.employeeNbr, u.name, c.name as CompanyName, ii.name as IndustryName from Users u JOIN u.idCompany c JOIN c.idIndustry i JOIN FETCH i.i18nIndustries ii WHERE u.idUserName = :username ]
What i understand from this hql is u want to fetch list of i18nIndustries ii along with that perticuler idIndustry i correct?
But when you use JOIN FETCH on any object you have to select the main object to hold child object, in your case main object is idIndustry. JOIN FETCH i.i18nIndustries ii means "when fetching idIndustry, also fetch the i18nIndustries, linked to the idIndustry". But your query doesn't fetch idIndustry So the fetch makes no sense.
In select either u have to fetch i also so that the owner (i) of the fetched association (i.i18nIndustries ii) will present in the select list. as your error is
query specified join fetching, but the owner of the fetched
association was not present in the select list
or remove JOIN FETCH just use JOIN
So keeping in mind u want list of i18nIndustries ii, Your query will become :(added i in select)
select u.idUserName, u.isActiveInd, u.employeeNbr, u.name, c.name as
CompanyName, i,ii.name as IndustryName from Users u JOIN u.idCompany c
JOIN c.idIndustry i JOIN FETCH i.i18nIndustries ii WHERE u.idUserName
= :username

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

JPA/Hibernate ignoring JOIN FETCH with condition on fetched data

I use Spring (no Boot), Spring Data JPA, Hibernate and H2 in-memory DB.
There are 2 entities: User and Post.
Post has attribute Set<User> users.
I have method that should get posts with users that have a name and then print all the posts and all their properties.
When i get posts and try to print users of these posts in the main (testing) class, it ends with LazyInitializationException. That is expected.
Using FetchType.EAGER works, as well as doing the printing in the service withing transaction or using OpenSessionInViewFilter.
However, all these solutions have their drawbacks, so I wanted to created separate method in a repository that would JOIN FETCH the users.
I need users that have a name, so my code looks like this:
#Query("SELECT p FROM Post p JOIN FETCH p.users u WHERE u.name is not null")
Collection<Post> findAllWithNamedUsers();
In this case, the JOIN FETCH is probably ignored, because I get the LazyInitializationException again:
Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize
a collection of role: example.entity.Post.users, could not initialize proxy - no Session
I tried experimenting with the query (no matter if it does what I need or not)...
#Query("SELECT p FROM Post p JOIN FETCH p.users u WHERE u.is not null") does not work
#Query("SELECT p FROM Post p JOIN FETCH p.users u") does not work
#Query("SELECT p FROM Post p LEFT JOIN FETCH p.users u") DOES work
#Query("SELECT p FROM Post p LEFT JOIN FETCH p.users u WHERE u.is not null") does not work
What happens here, why does it behave like this and what can I do to make it working with the condition?

Using "having" on join clause of Spring JPA specification

I have a repository of Users; each User has a OneToMany relationship with a collection of Posts.
How can I filter (and page) the repository of users by the number of posts they have using JPA specifications?
My initial attempt looked as such:
public static Specification<User> receiptNumberGreaterThanOrEqualTo(int numberToFilterBy) {
return (users, query, cb) -> greaterThanOrEqualTo(cb.count(users.get("posts")), Long.valueOf(numberToFilterBy));
}
This however caused an error to the effect of:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ')>=9 limit 100'
This is because the #OneToMany join expects a HAVING clause to be used rather than WHERE, i.e. select * from users outer left join posts on users.id = posts.id having(count(posts) > someNumber) where user.id = "some-id"
vs
select * from users outer left join posts on users.id = posts.id where user.id = "something" and count(posts) > someNumber;
I can generate a query with the having keyword by creating a join between user and posts, and then doing as follows:
query.having(cb.greaterThanOrEqualTo(cb.count(joinOfPosts), Long.valueOf(numberToFilterBy))), but the type of this is CriteriaQuery, and I'm unsure how to turn this into a Specification.
I solved this by using the size() method on the criteriaBuilder,
ie.
(users, query, criteriaBuilder) -> criteriaBuilder.greaterThanOrEqualTo(criteriaBuilder.size(users.get("posts")), numberToFilterBy);

How can I limit the results in a PagingAndSortingRepository #Query?

Frameworks: Spring 4.0.7 and Hibernate 4.3.6
We are having problems with a PagingAndSortingRepository #Query taking an excessive amount of time to complete as it appears to be running through recordset data and building structures before factoring in page data limits.
In non-joined scenarios, the fetch first n rows appears as it should, and the resultset is much smaller making the selection very snappy on the first batch of pages.
The initial problem we ran into was that we were unable to do a #Query with an inner join fetch (to force the join) on detail records. Removing the fetch obviously gets us off the ground, but creates a nested SQL loop which is not desirable.
To get around this, we added countQuery which got us the proper count back, but now appears to load the entire structure in memory before returning the paged results. Turning on debugging shows the records being loaded in. The fetch first n rows has not been added.
Having nested SQLs running in a loop is not an option for us, so removing the fetch does not help.
#Query(value = "select e from InvoiceHistoryHeader e inner join fetch e.details f where e.company = ?1 and e.division = ?2 and e.customerNumber = ?3)", countQuery = "select count(e) from InvoiceHistoryHeader e where e.company = ?1 and e.division = ?2 and e.customerNumber = ?3)")
Page<InvoiceHistoryHeader> findByCustomerNumber(String company, String division, String customerNumber, Pageable pageable);
Can anyone provide a solution to this problem, or give a workaround that may help? I can provide more detail as needed.
EDIT (11/11/2014)
Hibernate output (no fetch):
Hibernate: select count(trim(invoicehis0_.HH_TID)) as col_0_0_ from invhh invoicehis0_ where trim(invoicehis0_.HH_CO)=? and trim(invoicehis0_.HH_DIV)=? and trim(invoicehis0_.HH_CUS)=? and trim(invoicehis0_.HH_BR)=?
Hibernate: select invoicehis0_.hh_tid as hh_tid1_7_, trim(invoicehis0_.HH_CO) as hh_co2_7_, trim(invoicehis0_.HH_CUS) as hh_cus3_7_, trim(invoicehis0_.HH_DTI) as hh_dti4_7_, trim(invoicehis0_.HH_DIV) as hh_div5_7_, trim(invoicehis0_.HH_ORD) as hh_ord6_7_, trim(invoicehis0_.HH_BR) as hh_br7_7_ from invhh invoicehis0_ where trim(invoicehis0_.HH_CO)=? and trim(invoicehis0_.HH_DIV)=? and trim(invoicehis0_.HH_CUS)=? and trim(invoicehis0_.HH_BR)=? fetch first 20 rows only
Hibernate: select details0_.hd_tid as hd_tid2_7_0_, trim(details0_.HD_SEQ) as hd_seq1_6_0_, trim(details0_.HD_TID) as hd_tid2_6_0_, details0_.hd_seq as hd_seq1_6_1_, details0_.hd_tid as hd_tid2_6_1_, trim(details0_.HD_DTL) as hd_dtl3_6_1_, trim(details0_.HD_LIN) as hd_lin4_6_1_ from invhd details0_ where details0_.hd_tid=?
Hibernate: select details0_.hd_tid as hd_tid2_7_0_, trim(details0_.HD_SEQ) as hd_seq1_6_0_, trim(details0_.HD_TID) as hd_tid2_6_0_, details0_.hd_seq as hd_seq1_6_1_, details0_.hd_tid as hd_tid2_6_1_, trim(details0_.HD_DTL) as hd_dtl3_6_1_, trim(details0_.HD_LIN) as hd_lin4_6_1_ from invhd details0_ where details0_.hd_tid=?
<above two SQL repeated 18 more times>
Hibernate output (fetch and countQuery):
Hibernate: select invoicehis0_.hh_tid as hh_tid1_7_0_, details1_.hd_seq as hd_seq1_6_1_, details1_.hd_tid as hd_tid2_6_1_, trim(invoicehis0_.HH_CO) as hh_co2_7_0_, trim(invoicehis0_.HH_CUS) as hh_cus3_7_0_, trim(invoicehis0_.HH_DTI) as hh_dti4_7_0_, trim(invoicehis0_.HH_DIV) as hh_div5_7_0_, trim(invoicehis0_.HH_ORD) as hh_ord6_7_0_, trim(invoicehis0_.HH_BR) as hh_br7_7_0_, trim(details1_.HD_DTL) as hd_dtl3_6_1_, trim(details1_.HD_LIN) as hd_lin4_6_1_, details1_.hd_tid as hd_tid2_7_0__, trim(details1_.HD_SEQ) as hd_seq1_6_0__, trim(details1_.HD_TID) as hd_tid2_6_0__ from invhh invoicehis0_ inner join invhd details1_ on trim(invoicehis0_.HH_TID)=details1_.hd_tid where trim(invoicehis0_.HH_CO)=? and trim(invoicehis0_.HH_DIV)=? and trim(invoicehis0_.HH_CUS)=? and trim(invoicehis0_.HH_BR)=?
When using fetch in join and paging Hibernate does in-memory pagination, which means it loads all records in memory.
Do not use fetch joins with paging.

JPA entitymanager remove operation is not performant

When I try to do an entityManager.remove(instance) the underlying JPA provider issues a separate delete operation on each of the GroupUser entity. I feel this is not right from a performance perspective, since if a Group has 1000 users there will be 1001 calls issued to delete the entire group and itr groupuser entity.
Would it make more sense to write a named query to remove all entries in groupuser table (e.g. delete from group_user where group_id=?), so I would have to make just 2 calls to delete the group.
#Entity
#Table(name = "tbl_group")
public class Group {
#OneToMany(mappedBy = "group", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#Cascade(value = DELETE_ORPHAN)
private Set<GroupUser> groupUsers = new HashSet<GroupUser>(0);
Simple answer is yes.
If you want to delete a Group and you know there are tons of records in GroupUser table, then it is much better to create a delete query that will do all in one batch instead of one and one.
If you have no cascading on the underlying database, (or even if you do) its good practice to do it in correct order.
So delete the GroupUser first.
Assuming you have a the Group object you want to delete.
int numberDeleted = entityManager.createQuery("DELETE FROM GroupUser gu WHERE gu.group.id=:id").setParameter("id",group.getId()).executeUpdate();
The returning int shows how many records where deleted.
Now you can finally delete Group
entityManager.remove(group);
entityManager.flush();
UPDATE
Seems like #OnDelete on the #OneToMany does the trick
Since the GroupUser may have cascades as well, I don't think there is a way to tell hibernate to batch-delete them via configuration.
But if you are certain there are no cascade=DELETE on GroupUser, feel free to issue an HQL/JPA-QL query:
DELETE FROM GroupUser WHERE group=:group
If there are cascades, handle them with a query as well.

Resources