Spring Boot Criteria API Subquery correlation Invalid Path - spring

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

Related

Spring data jdbc mapping not working if use not primary key

I have 2 entities:
#Data
#Table("main_entities")
public class MainEntity {
#Id
private Long id;
private String anotherId;
#MappedCollection(idColumn = "main_entity_id")
private SecondEntity secondEntity;
}
#Data
#Table("second_entities")
public class SecondEntity {
#Id
private Long id;
private Long mainEntityId;
}
And exists the repository:
public interface MainEntityRepository extends CrudRepository<MainEntity, Long> {
#Query("SELECT * FROM main_entities WHERE another_id = :anotherId")
Optional<MainEntity> findByAnotherId(#Param("anotherId") String anotherId);
}
When I use the MainEntityRepository#findById(Long) - the SecondEntity is available, when I use the MainEntityRepository#findByAnotherId(String) - the SecondEntity is null
Update 2021.12.15:
if set the
#MappedCollection(idColumn = "main_entity_id")
private Set<SecondEntity> secondEntities;
Its allows to get the mapped collection via MainEntityRepository#findByAnotherId(String)
Spring Data JDBC loads 1:1 relationships with a single join and expects you to do the same when you specify a custom query.
In order to avoid ambiguities you have to use column aliases which prefix the columns with the property name of the 1:1 relation ship plus an _.
So your select should look like this:
SELECT M.ID, M.ANOTHER_ID, S.ID AS SECONDENTITY_ID, S.MAIN_ENTITY_ID AS SECONDENTITY_MAIN_ENTITY_ID
FROM MAIN_ENTITIES M
JOIN SECOND_ENTITIES S
ON M.ID = S.MAIN_ENTITY_ID
WHERE ANOTHER_ID = :anotherId
I created a complete example.
Side note: I recommend not to have an id on the non-aggregate-root entities, nor to have the reference back to the aggregate root in these entities. See Spring Data JDBC - How do I make Bidirectional Relationships?
so you want to fetch the second entity together with your main entity with your custom method?
I thinkt it has to do with the fetch type of your main entity. It is lazy by default and if you want to load both entitys you can try to set the fetch type to eager for the second entity field in your main entity. But be aware that this is not always the best option but rather a quick fix. See here for more information about fetch types.
You can also try using the join fetch as described in the accepted answer here to achieve your requested behaviour. I think that this would be the best solution.
I hope I got your question right if not please try to explain with further detail.

Entity JOIN using Spring Data Specification

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.

Spring JPA Repository Sub-Query In Native Query

I am trying to run a native query on a repository method so that it returns the results with some counts. It was too complicated to do with JPQL, so I opted for a native query instead.
Repository
#RepositoryRestResource(collectionResourceRel="projects", path="projects")
interface ProjectRepository extends BaseRepository<Project, Long>, ProjectRepositoryCustom {
#Query(
value="SELECT p.id, p.user_id, p.title, p.description, p.created_on, p.version,(SELECT COUNT(0) FROM projectparts WHERE project_id = p.id) AS parts,(SELECT COUNT(0) FROM requests WHERE project_id = p.id) AS requests FROM projects AS p ORDER BY ?#{#pageable}",
countQuery="SELECT COUNT(0) FROM projects",
nativeQuery=true
)
Page<Project> findAll(Pageable pageable)
}
The entity has 2 properties annotated with #Transient so that the info is not persisted to the database. All the data comes back fine except the 2 transient properties which return null for the values. When I copy the query from the console and paste it in MySQL Workbench, the results are as expected and I see the counts that I need. Anyhow, not sure if there is anything else that needs to be done in order to get this native query to work as an annotation. I hard coded a value in the sub-query SELECT 55 FROM... just to see if it was a problem with the count and it still returned as null. I ran the query in Workbench and it works fine.
I've tried changing the transient property type from Integer, Long, BigInteger, long, int... and none of that made a difference. Since I'm using Groovy, I also tried def to let Groovy infer the type and that didn't work either.
I also tried running the project from the terminal instead and it still didn't work. I've tried it on a Mac and Linux and had no luck with displaying the results of the counts.
This will not work. You could use an SQLConstructorExpression however the returned instances would be unmanaged which is a major drawback.
An better option is to create a simple DB view which holds the pieces of summary info for the Project. You can them map the Project entity to both it's table and the associated summary view using the #SecondaryTable functionality of JPA.
https://en.wikibooks.org/wiki/Java_Persistence/Tables#Example_mapping_annotations_for_an_entity_with_multiple_tables
An added benefit is that you can sort and query on the summary values as for any other property.
Updated mapping:
#Entity
#Table(name = "projects")
#SecondaryTable(name = "projects_summary_vw")
public class Project{
//use Integer rather than int to avoid issue outlined here:
//http://stackoverflow.com/a/37160701/1356423
#Column(name = "parts", table = "projects_summary_vw",
insertable="false", updateable="false")
private Integer partsCount;
#Column(name = "requests", table = "requestsCount"
insertable="false", updateable="false")
private Integer requestsCount;
//other mappings as required
}
No Custom query required:
#RepositoryRestResource(collectionResourceRel="projects",
path="projects")
interface ProjectRepository extends BaseRepository<Project, Long>,
ProjectRepositoryCustom {
}
An alternative non-JPA compliant solution may be to use some vendor specific extension rather than a view. Hibernate for example has an #Formula annotation which could be used:
https://docs.jboss.org/hibernate/orm/5.1/javadocs/org/hibernate/annotations/Formula.html
#Entity
#Table(name = "projects")
public class Project{
#Formula("my count query as native sql")
private Integer partsCount;
#Formula("my count query as native sql")
private Integer requestsCount;
//other mappings as required
}

Spring Data find by inner relation

My question is about the way Spring data is generating the query .
I have two entities : Message , Sender
#Entity
public class Message extends BaseEntity {
#ManyToOne
protected Account sender;
}
I have a call to
messageDao.findBySenderId(Long id)
The result is query all columns from the two two table with a left outer join between the two tables , but my expectation was simply to just select from message table where sender_id = the passed value.
So is there a way to force selecting only the first message entity and not to join with the other one? I want simple condition in the where clause
by using findBy not custom #Query
You will need a repository like (untested) :
#Repository
public interface MessageRepository extends JpaRepository<Message, Long> {
Message findFirstBySenderId(Long id);
}
See repositories.query-methods
I think Hibernate is doing a LEFT JOIN because your #ManyToOne is optional = true (default).
Try:
#ManyTone(optional = false)
And you will see Hibernate doing a query without a JOIN, as you expected.
You can use fetch type LAZY for the associations types:
#ManyToOne(optional = false, fetch = FetchType.LAZY)

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

Resources