How to use multiple JOINs with CriteriaBuilder to build Predicate? - spring-boot

I have table A that has an attribute that is an entity in table B that in turn is connected to a table C.
The (working) SQL query looks like this:
SELECT a.* from A a
LEFT JOIN B b ON a.b_id=b.id
LEFT JOIN C c ON b.c_id=c.id where c.attribute=VALUE;
Basically the VALUE is what Im filtering on. There is a one-to-one relationship from A->B and a one-to-one relationship from B->C.
There are other parameters Im also filtering on so I have a Specification class that generates a Predicate for each parameter that is passed in to build a list of predicates which is then ANDed together at the end. The Specification is being called from my Repository using something like findAll(MySpecificationClass.search(params)).
Im having a hard time understanding how to write this SQL query using CriteriaBuilder inside my Specification class.
NOTES:
This is in a Spring Boot application written in Kotlin.
My specification class is a singleton so I can't autowire EntityManager for example.
I tried to use a chain of get()s to navigate from A, through B to C but that doesn't work - it seems to return all records regardless.
My project is using spring-data-jpa 2.2.6

Through the help of a colleague I was able to build this (also should have been INNER JOINS).
The specific predicate returned from my Specification class is:
val joinToB: Join<A, B> = root.join("B", JoinType.INNER)
val joinToC: Join<B, C> = joinToB.join("C", JoinType.INNER)
cb.equal(joinToC.get<Long>("attribute"), VALUE)

Related

Run complex SQL Query with spring boot repositories

i have a little complex scenario using spring data and jpa currently.
My data structure is like:
And i like to create a filter: give me all events which belongs to a list of structures, within a given period and is assigned to a list of categories.
I was able to create a sample SQL statement:
select * from event e inner join period p on e.period_id=p.id
inner join category_item_ids ci on e.id=ci.item_ids
inner join category c on ci.category_id=c.id
inner join event_assignment_structure_ids es on es.event_assignment_id=e.id
where p.start between '2020-05-01 12:00:00.000000' and '2020-11-14 12:00:00.000000' and
c.id in (1) and
es.structure_ids in (1,2)
But my objects are currently not wired together by all the JPA annotations.
e.g. the "Same ID" is something i did by convention to make the parts a little more independend.
So using the JPA way is currently no option i guess due to the missing relations.
Introducing them will be also quite a lot of work.
So i was wondering if there is a possiblity to run the sql query directly (i could use native query, but thats also no option, cause the filter values are not always given, so i need 13 native queries)
I used enityManager and query Builder in the past, but thats also no option due to the missing jpa relations.
Any ideas are much welcome :-)
Regards
Oliver

join more than one table in spring jparepository

I am trying to fetch record by doing a join. I am new to spring jparepository.
I understand that there is separate repository for each entity(table) where when i implement i need to define the entity and datatype of primary key.
Could anyone please suggest how can I fetch record by joining two tables.
I have two repo as below:
public interface AEntityRepository extends JpaRepository<AEntity, Integer>
public interface BEntityRepository extends JpaRepository<BEntity, Integer>
I want to join above two entity(AEntity, BEntity).
I know I can have custom query using something like below:
#Query("SELECT ****** FROM AEntity ae")
AEntity findCustomrRecords();
However can I write the same kind of query (join query) with join.
Do i need to have a separate repository implementing some other class.
Can anyone please help.
I am using mysql.
I understand that there is separate repository for each entity(table)
This is a very common misunderstanding. You do not want to have a repository per entity, but per aggregate root. See http://static.olivergierke.de/lectures/ddd-and-spring/
Regarding your specific problem at hand: Creating a custom method in your repository interface and annotating it with a JPQL should do the trick. So you get something like:
#Query("select a from BEntity b join b.a a where b.foo = :foo")
AEntity getAllFooishAs(String foo);
You can use any join syntax JPQL offers in the query.

Using referencing class field as filter in Spring Data Repository

Context: I have three models, Owner, Property and Community. Property has a reference to its Owner, and another one to the Community.
I need to make the following query: find all the owners in a community which meet some criteria (floor number, property letter, etc, all the fields of the criteria are inside the Property class)
Is there any way to implement this in a repository without creating a bidirectional relationship or writing a native query? Something like:
Set<Owners> findAllByCommunityAndProperty_floorNumberAndProperty_letter(Community community, Property property);
(I would need a bidirectional relationship to make the query above)
You can use a query like this
SELECT o
FROM Property p
INNER JOIN property.owners o
WHERE p. ...
See http://www.objectdb.com/java/jpa/query/jpql/from#INNER_JOIN_ for various examples of join syntax.
In Spring Data JPA you will probably use the #Query annotation to bind that query to a method.

Springboot JPA #Query Rest Giving PersistentEntity must not be null

I am trying to put together a simple rest service with springboot 1.4.3. I have it up and running with simple queries like findByRecid, however when I try to do a #Query statement on that same entity, I get the below error message
{"cause":null,"message":"PersistentEntity must not be null!"}
Further, if I use a fully qualified name for the entity in the query, Intellij tells me that the class isn't an entity, even though it's marked with #Entity and works with the standard springboot queries. Please assist if possible - I've been trying to figure this one out for days. Below is the query for your reference
#Query("SELECT new com.test.domain.ReceivableBeans.RecAgeBucketGroupAmountSum(r.agebucket, sum(r.amount)) from com.test.domain.Receivable as r GROUP BY r.agebucket")
List<com.test.domain.ReceivableBeans.RecAgeBucketGroupAmountSum> recByAgeBucket();
Try:
#Query("select new ReceivableStats(r.agebucket, sum(r.amount)) from Receivable r group by r.agebucket")
List< ReceivableStats> recByAgeBucket();
or name your aliases in the query, e.g.
sum(r.amount) as amount
and have your method return an Object[].

FetchTypes in Spring Data JPQL Query (MultipleBagFetchException)

I am using Spring Data and defining following query:
#Query("SELECT u FROM AppUser u LEFT OUTER JOIN fetch u.userRights a LEFT OUTER JOIN fetch u.userGroups g LEFT OUTER JOIN fetch u.userGroups ug LEFT OUTER JOIN FETCH ug.groupRights where u.login = :login")
public Optional<AppUser> findOneWithCompleteRights(#Param("login") String login);
As you might see, I want to get back the logged in user with all his access rights. While starting the spring application, it runs into:
Caused by: javax.persistence.PersistenceException: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags
I have checked following:
Multiple fetches with EAGER type in Hibernate with JPA
If I change all#XXXToMany Types to java.util.Set, it works, but I would like to decide the type on my own...
The other annotations of linked solution (see bottom) seem to be ignored if attached to the #Query method. Second would not make sense, anyway.
Load each collection separately using subselect #Fetch(FetchMode.SELECT)
Force usage of list instead of bag by adding index column #IndexColumn(name="LIST_INDEX")
Does anybody have another solution rather than setting the type to Set?
I had that problem also.
It happens when the class to load has more than on property of type List mapped.
You can resolve that,
by changing the type of
AppUser.userRights and AppUser.userGroups
from java.util.List to java.util.Set.
Not using Set here instead of List is exactly what Vlad Mihalcea is describing in this post and in some answers on StackOverflow. In his post he says to use separate queries.
But he is giving the solution not with the #Query-Annotation provided by the Spring-Data Project rather using JPQL-statements and the entityManager.
So to achieve the separate Query-Execution I would have thought, that the #Fetch(FetchMode.SELECT) (or SUBSELECT, not yet sure about the difference) would be a proper solution. But not for the #Query-Annotation but for the attribute in the class.
So in your case maybe:
class AppUser {
....
#OneToMany
#Fetch(FetchMode.SELECT)
List<UserRights> userRights
....
}
But trying that in my own project did not work, either. So another solution might be to somehow create multiple queries in the Statement of the #Query-Annotation, to act according Vlad's suggestion Not sure if that is possible in the Annotation-Parameter itself or if there has to be two Methods with two annotations.
#Query(
"SELECT u FROM AppUser u
LEFT OUTER JOIN fetch u.userRights a
LEFT OUTER JOIN fetch u.userGroups g
LEFT OUTER JOIN fetch u.userGroups ug
LEFT OUTER JOIN FETCH ug.groupRights where u.login = :login"
)
public Optional<AppUser> findOneWithCompleteRights(#Param("login") String login);

Resources