Entity JOIN using Spring Data Specification - spring

I'm currently messing around with Spring Data JPA Specifications. I came across the following question: How can I define the following SQL Join with just JPA Specifications?
SELECT * FROM competition c
LEFT JOIN participation p ON c.id = p.competition_id
LEFT JOIN team t ON p.team_id = t.id
WHERE t.name = 'WDB'
Note that Competition, Participation and Team are JPA Entities (#Entity)!

A JPQL query could be as follows;
SELECT c FROM competition c
LEFT JOIN c.participation p
LEFT JOIN p.team t
WHERE t.name = 'WDB'
The above assuming that from the original description of the problem, that the entities are defined like in the following (accessors and other details omitted).
#Entity
public class Competition
#ManyToOne
public List<Participation> participation;
#Entity
public class Participation
#ManyToOne
public List<Team> team;
#Entity
public class Team
private String name;
If your return object is not an Competition or other entity object, then you could define another transfer class and make use of JPQL's NEW operation to return an instance of it.

Related

JPA #OneToMany but with only one rekord

I have a table in the database that has a #OneToMany link to another table, JPA in standard form will return me the values from the other table as a list, however I would like to get the records as :
SELECT * FROM a LEFT JOIN b ON b.a_id = a.id
so if there are 2 records in the table "b" then I should get a list of 2 elements, not a one element list with a list inside that has values from table "b". Additionally, I will point out that I care to implement this by the function "Page findAll(#Nullable Specification spec, Pageable pageable);".
Example entity:
#Entity
public class A {
private Long id;
#OneToMany
private List<B> b;
soo i like to look like this
#Entity
public class A {
private Long id;
#OneToOne
private B b;
But when theres more that one B i will get second rekord instede of error.
What can I do to achieve this?

Spring Boot Criteria API Subquery correlation Invalid Path

I am having an invalid path issue with Hibernate and Spring JPA Criteria API. I have the following error:
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'generatedAlias4.applicationId' [select generatedAlias0 from ie.ul.ethics.scieng.applications.models.applications.Application as generatedAlias0 where :param0 in (select generatedAlias1.username from ie.ul.ethics.scieng.applications.models.applications.SubmittedApplication as generatedAlias2 inner join generatedAlias2.assignedCommitteeMembers as generatedAlias3 inner join generatedAlias3.user as generatedAlias1 where generatedAlias3.applicationId=generatedAlias4.applicationId)
Basically, the alias generatedAlias4 is presumably an alias that was meant to appear from a JOIN performed somewhere but the alias is not defined anywhere in the SQL leading to the error.
The SQL I am trying to emulate is:
SELECT a
FROM SubmittedApplication a
WHERE 'test' IN(SELECT m.username FROM SubmittedApplication a1 JOIN AssignedCommitteeMember assigned JOIN User WHERE a.applicationId = a1.applicationId);
I understand that might not be correct HQL (quickly wrote it to give a rough idea) but I am using the CriteriaQuery and CriteriaBuilder API to dynamically create the query.
The code creating the query is as follows:
CriteriaQuery<Object> query = criteriaBuilder.createQuery();
Root<SubmittedApplication> applicationRoot = query.from(SubmittedApplication.class);
Subquery<String> subQuery = query.subquery(String.class);
Root<SubmittedApplication> subApplication = subQuery.from(SubmittedApplication.class);
Root<SubmittedApplication> correlated = subQuery.correlate(applicationRoot);
Join<?, ?> assignedJoin = subApplication.joinList("assignedCommitteeMembers");
Join<?, ?> assignedUserJoin = assignedJoin.join("user");
subQuery.select(assignedUserJoin.get("username"));
subQuery.where(criteriaBuilder.equal(assignedJoin.get("applicationId"), correlated.get("applicationId")));
query.select(applicationRoot)
.where(criteriaBuilder.in(criteriaBuilder.literal(criteria.getValue())).value(subQuery));
return query.getRestriction();
It returns a Predicate for use in a Specification to search using a repository extending the JpaSpecificationExecutor interface. I used correlate to correlate the application root into the subquery, which theoretically means the last of the subquery where statement should be where generatedAlias3.applicationId=generatedAlias0.applicationId and not generatedAlias3.applicationId=generatedAlias4.applicationId
The query is meant to ask, find all Applications where the username 'test' exists in the list of AssignedCommitteeMembers. The SubmittedApplication class is a subclass of the Application class (a class that represents an application form being created on the system) that contains a list with OneToMany mapping of these AssignedCommitteeMembers. The first I noticed, selects from the base Application class. Shouldn't it be selected from the SubmittedApplication class? Could this be the issue? select generatedAlias0 from ie.ul.ethics.scieng.applications.models.applications.Application as generatedAlias0 instead of select generatedAlias0 from ie.ul.ethics.scieng.applications.models.applications.SubmittedApplication as generatedAlias0
The entity for the SubmittedApplication class (with irrelevant properties stripped. The applicationId field is contained in the superclass Application. It is a String ID separate to the database ID used for application management):
#Entity
public class SubmittedApplication extends Application {
/**
* The list of assigned committee members
*/
#OneToMany(cascade = CascadeType.ALL)
protected List<AssignedCommitteeMember> assignedCommitteeMembers;
.. other attributes
}
The AssignedCommitteeMember class is here:
#Entity
public class AssignedCommitteeMember {
/**
* The database ID of this object
*/
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* The ID of the application the member is assigned to
*/
private String applicationId;
/**
* The committee member that is assigned
*/
#OneToOne
private User user;
/**
* Determine if the committee member has finished their review
*/
private boolean finishReview;
I hope I gave enough detail. If more information is needed I am happy to give it. Any help is appreciated

Query in arraylist by one element in spring boot h2

I have a ClassRoom class, in my Spring Boot application. Here is what it looks like :
public class ClassRoom {
#Id #GeneratedValue Long classRoomID;
ArrayList<User>adminList=new ArrayList<>();
}
And in my ClassRoomRepository class, I have :
public interface ClassRoomRepository extends JpaRepository<ClassRoom,Long> {
#Query("select ClassRoom from ClassRoom c where c.adminList = ?1")
ArrayList<ClassRoom> findByAdminList(ArrayList<User> adminList);
/*
#Query("select ClassRoom from ClassRoom c where c. = ?1")
ArrayList<ClassRoom> findByAdmin(User admin);
*/
}
I can query to select ClassRoom where ArrayList of ClassRoom gets passed parameter.
But I want to query to select ClassRoom where I pass only one User as parameter and returns ArrayList of ClassRoom.(Commented section-nothing done so far)
If it is possible in this interface, how can I do so?
Asuming you habe many-to-many association using join table, this is what you need,
#Query(value = "SELECT DISTINCT cr FROM ClassRoom cr JOIN cr.admins a WHERE a = ?1")
List<ClassRoom> findAllByAdmin(final Admin admin);
When mapping collection , Hibernate requires it to declare it using interface type such as List , Set , Map etc. But you are using ArrayList to declare the admin list , I am guessing the whole list will be stored to a single column with some binary data type in DB . No one would store the data in this funny way when using RDBMS unless you have strong reason to do it.
From what you describe , ClassRoom and User seem to be many to many. For demonstration convenience , I would map it as #ManyToMany:
#Entity
#Table
public class ClassRoom {
#ManyToMany
#JoinTable(name ="classroom_user",
joinColumns = #JoinColumn(name = "classroom_id"),
inverseJoinColumns = #JoinColumn(name = "user_id"))
private List<User> admins = new ArrayList();
}
#Entity
#Table
public class User{
#ManyToMany(mappedBy = "admins")
private List<ClassRoom> classRooms = Lists.newArrayList();
}
Then given an user admin ,to get all of his administrative class rooms :
#Query("select distinct cr from ClassRoom cr left join fetch cr.admins admin where admin = ?1 ")
public List<ClassRoom> findByAdmin(User admin);
Or :
#Query("select distinct cr from ClassRoom cr where ?1 member of cr.admins")
public List<ClassRoom> findByAdmin(User admin);

Sort by joined table's field Spring JPA

I have two entity classes Request,User:
//Ommiting some annotations for brevity
public class User{
private Long id;
private String name;
private Integer age;
}
public class Request{
private Long id;
private String message;
private Date createTime;
#ManyToOne
#JoinColumn(name="user_id")
private User user;
}
I can sort request list by create time :
Sort = new Sort(Direction.ASC,"createTime");
Is there a possible way to sort request list by User's name? Like:
Sort = new Sort(Direction.ASC,"User.name");
Yes. new Sort(Direction.ASC,"user.name"); should work just fine.
Spring Data JPA will left outer join the User to the Request and order by the joined column's name resulting in SQL like this:
select
id, message, createTime
from
Request r
left outer join User u on u.id = r.user_id
order by
u.name asc
This works great on one-to-one and, like you have here, many-to-one relationships but because a left outer join is employed to implement the order by clause, if the joined entity represents a many relationship (like in a one-to-many), the Sort may result in SQL in which duplicate records are returned. This is the case because the Sort parameter will always result in a new left outer join even if the entity being joined is already joined in the query!
Edit Incidentally, there is an open ticket concerning this issue: https://jira.spring.io/browse/DATAJPA-776

Join 3 tables in Spring Jpa Data

I've been struggling lately to join 3 tables with spring data jpa. I have 3 entities, Series, Dossier and Item. Series has many Dossiers, and Dossier has many Items (Relationships). I do something like Series.join(Dossier_.series).join(Dossier_.items) and I end up with a Join set. I want to make the following query:
Select Items from Series,Dossier,Item
Where Series.Id=Dossier.seriesId
and Dossier.id=Item.dossierId
and series.projectId = :param
I can't express this statement with Spring Specifications and criteria api....Please shed some light
It is more a JPA question.
First, I always emphasize, you are not access "tables". You should view them as domain entities. Lot of misuse of JPA/Hibernate/other ORMs actually comes from direct "translate" of SQL or database concepts.
Back to your question, the answer is simple. First make sure you actually have the "relationships" in your domain entities. Storing IDs is not helping to build a concrete domain model. For example, you have something like :
#Entity
class Series {
#Id
Long id;
#OneToMany(mappedBy="series")
List<Dossier> dossiers;
}
#Entity
class Dossier{
#Id
Long id;
#ManyToOne
Series series;
#OneToMany(mappedBy="dossier"
List<Item> items;
}
#Entity
class Item{
#Id
Long id;
#ManyToOne
Dossier dossier;
}
The query is straight-forward:
select s.dossiers.items from Series s where s.projectId = :param
Or, if it is more reasonable to have only the #ManyToOnes and omit the #OneToManys, the query is still straight-forward:
from Item where i.dossier.series.projectId = :param
[Still rocky here] Maybe i didn't make myself clear.I know how to express the query in HQL.The problem is to use Spring Data's Specifications,with the help of the criteria api to build that query.
//Let's exampine the following piece of code
public class CustomItemSpecs {
public static Specification<Item> createSpecificationFromSearchForm(final SearchForm searchForm) {
return new Specification<Item>() {
#Override
public Predicate toPredicate(Root<Item> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
CriteriaQuery<Item> cq = cb.createQuery(Item.class);
CriteriaQuery<Series> sb = cb.createQuery(Series.class);
Root<Series> series = sb.from(Series.class);
Join<Series,Dossier> join1 = series.join(Series_.dossiers);
Join<Dossier, Item> join2 = join1.join(Dossier_.items);
}
}
}
As you can see i manage to do two seperate joins.The problem is when i want to join Series,Dossier And Items to execute the above query.Notice that join2 is a Dossier-Item set.I can't make criteria like cb.equals(join2.get(Series_.projectId),)
im using spring data interface projection .
example like this
note items must be interface class
Repository Class
#Repository
public interface ItemRepository extends JpaRepository<Items,Long> {
#Query(nativeQuery = true,value = "Select Items from Series,Dossier,Item Where Series.Id=Dossier.seriesId and Dossier.id=Item.dossierId and series.projectId = :param")
public Items findTransaksisByAccountIdOrderById(#Param("param") Long projectId);
}

Resources