Spring Data JPA #Query with Specification - spring-boot

I have a requirement to create a REST api. Api allows user to provide dynamic search criteria in URL. For example, let say I have NOTES table with column as Note_ID, NOTE_TEXT, STATUS, PERSON_ID. This table is used to keep notes of every person.
Now I want my REST api to be as https://server:host/MyApi/Notes?search=NoteText=='My Java adventure'. API should provide all notes having NOTE_TEXT as 'My Java adventure'. Similarly user can provide status also in url and also he can use operators as LIKE. I was able to do it via rsql parser as mentioned in https://www.baeldung.com/rest-api-search-language-rsql-fiql
Now I have additional requirement that based on user security person_id filter should be applied on query automatically.
I found that we can't have findBy method which can take Specification, Pageable and extra personId. For example I can't have a repository function as
findByPersonId(Specification spec, Pageable page, Long personId);
I thought of using SpEL to use it, but then I found that if we use #Query annotation on findBy method, Specifications are ignored.
Seems like there is no way I can have Specification and #Query both. I need to add more clauses using specification only. In reality my where clause is very complex which I have to append and getting it with Specification seems to be difficult. Its something like
Select * from NOTES where exists (select 'x' from ABC a where n.person_id = a.person_id)
Is there a way I can write #Query and also have Specification working on top of it?
Ideally I have achieve a query like
select * from test.SCH_FORUM_THREAD t
where exists (select 'x' from test.FORUM_THREAD_ACCESS fta, school.SCH_GROUP_PERSON gp
where gp.GROUP_ID = fta.GROUP_ID
and t.THREAD_ID = fta.THREAD_ID
and gp.PERSON_ID = :personId)
or exists (select 'x' from test.FORUM_THREAD_ACCESS fta
where fta.THREAD_ID = t.THREAD_ID
and fta.PERSON_ID = :personId);
So there are two exists clauses with or condition. I was able to make second exists by following How to write query(include subquery and exists) using JPA Criteria Builder
Now struggling with first exists as it has join also. Any idea how to do that with Specification.
Also as there are two exists, does that mean I need two specifications. Can I achieve it in one specification.

I was able to resolve it by creating a complex specification code. Something like
#Override
public Predicate toPredicate(Root<ForumThread> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
Subquery<ForumThread> subQuery = query.subquery(ForumThread.class);
Root<ForumThread> subRoot = subQuery.from(ForumThread.class);
Join<ForumThreadAccess, GroupPerson> fragpjoin = subRoot.join("groupPersons");
Predicate threadPredicate = builder.equal(root.get("threadId"), subRoot.get("threadId"));
Predicate personPredicate = builder.equal(fragpjoin.get("personId"), personId);
subQuery.select(subRoot).where(threadPredicate, personPredicate);
Predicate existsGroupPredicate = builder.exists(subQuery);
Subquery<ForumThreadAccess> subQuery1 = query.subquery(ForumThreadAccess.class);
Root<ForumThreadAccess> subRoot1 = subQuery1.from(ForumThreadAccess.class);
Predicate threadPredicate1 = builder.equal(root.get("threadId"), subRoot1.get("threadId"));
Predicate personPredicate1 = builder.equal(subRoot1.get("personId"), personId);
subQuery1.select(subRoot1).where(threadPredicate1, personPredicate1);
Predicate existsPersonPredicate = builder.exists(subQuery1);
return builder.or(existsGroupPredicate,existsPersonPredicate);
}
To make it work your entities should also have proper #OneToMany and #ManyToMany in place.
Thanks

Related

Spring And Kotlin Query

How can I achieve this query
select *
from table t
where name like '%Ami%'
order by (name = 'Ami') desc, length(col);
(just the sort part)
Using springframework Sort..
What I tried is
Sort.by(Sort.Direction.DESC, "name") // But I need to sort by name = 'Ami'
Sort.by(Sort.Direction.DESC, "name" = 'Ami'") // throws an error
JpaSort.unsafe(Sort.Direction.DESC, "name" = 'Ami'") // throws an error
Looks like the documentation has an example almost identical to your question:
https://docs.spring.io/spring-data/jpa/docs/2.4.5/reference/html/#jpa.query-methods.sorting
However, using Sort together with #Query lets you sneak in
non-path-checked Order instances containing functions within the ORDER
BY clause. This is possible because the Order is appended to the given
query string. By default, Spring Data JPA rejects any Order instance
containing function calls, but you can use JpaSort.unsafe to add
potentially unsafe ordering.

Spring JPA #Query Annatotion disadvantages

Hi I use spring jpa to access my data in my spring boot project.I am wondering that is there any difference between #Query annatotation and critearia api in jpa.Are they totaly same or is there any difference(Their writing styles are different ,and I mean any performance or other issue between them)
Mostly I prefer #Query annotation it looks simple.Or any other option some one can advice like #Query or criteria api in spring jpa.And is there any disadvantages of #Query style?
#Query("SELECT u FROM User u WHERE u.status = 1")
Collection<User> findAllActiveUsers();
List<Book> findBooksByAuthorNameAndTitle(String authorName, String title) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Book> cq = cb.createQuery(Book.class);
Using #Query we can pass static query or pre compiled query so we can perform both select and non-select operations on the data
where as Criteria is suitable for executing Dynamic Queries such requirements occurs when data are know at run time
but using criteria api we can only perform select operations on the data.
For example
#Query(value = "SELECT u FROM User u")
List<User> findAllUsers(Sort sort);
We can also work with pre compiled query using #Query
For Example
#Query("SELECT u FROM User u WHERE u.status = :status and u.name = :name")
User findUserByStatusAndNameNamedParams(
  #Param("status") Integer status,
  #Param("name") String name);)
Dynamic queries like
Criteria cr = session.createCriteria(Employee.class);
// To get records having salary more than 2000
cr.add(Restrictions.gt("salary", 2000));
// To get records having salary less than 2000
cr.add(Restrictions.lt("salary", 2000));
Actual use of Dynamic queries comes when we'll encounter the need for building SQL statements based on conditions or data sets whose values are only known at runtime. And, in those cases, we can't just use a static query So we can't just use the #Query annotation since we can't provide a static SQL statement.In such case we use Criteria API
For more info follow the link provided
#Query and Criteria

Mapping many-to-many IN statement into JPA (Spring Boot)

I have created two entities in JPA, Listing and ItemType - these exist in a many-to-many relationship (Hibernate auto-generates a junction table). I'm trying to find the best way to create a query which accepts a dynamic list of item type Strings and returns the IDs of all listings which match the specified item types, but I am a recent initiate in JPA.
At present I'm using JpaRepository to create relatively simple queries. I've been trying to do this using CriteriaQuery but some close-but-not-quite answers I've read elsewhere seem to suggest that because this is in Spring, this may not be the best approach and that I should be handling this using the JpaRepository implementation itself. Does that seem reasonable?
I have a query which doesn't feel a million miles away (based on Baeldung's example and my reading on WikiBooks) but for starters I'm getting a Raw Type warning on the Join, not to mention that I'm unsure if this will run and I'm sure there's a better way of going about this.
public List<ListingDTO> getListingsByItemType(List<String> itemTypes) {
List<ListingDTO> listings = new ArrayList<>();
CriteriaQuery<Listing> criteriaQuery = criteriaBuilder.createQuery(Listing.class);
Root<Listing> listing = criteriaQuery.from(Listing.class);
//Here Be Warnings. This should be Join<?,?> but what goes in the diamond?
Join itemtype = listing.join("itemtype", JoinType.LEFT);
In<String> inClause = criteriaBuilder.in(itemtype.get("name"));
for (String itemType : itemTypes) {
inClause.value(itemType);
}
criteriaQuery.select(listing).where(inClause);
TypedQuery<Listing> query = entityManager.createQuery(criteriaQuery);
List<Listing> results = query.getResultList();
for (Listing result : results) {
listings.add(convertListingToDto(result));
}
return listings;
}
I'm trying to understand how best to pass in a dynamic list of names (the field in ItemType) and return a list of unique ids (the PK in Listing) where there is a row which matches in the junction table. Please let me know if I can provide any further information or assistance - I've gotten the sense that JPA and its handling of dynamic queries like this is part of its bread and butter!
The criteria API is useful when you need to dynamically create a query based on various... criteria.
All you need here is a static JPQL query:
select distinct listing from Listing listing
join listing.itemTypes itemType
where itemType.name in :itemTypes
Since you're using Spring-data-jpa, you just need to define a method and annotate it with #Query in your repository interface:
#Query("<the above query>")
List<Listing> findByItemTypes(List<String> itemTypes)

spring data jpa specification join fetch is not working

I am trying to use Spring Data JPA Specificaiton to query data, but I got some problem here.
The Java code is as below:
List<NoticeEntity> studentNoticeEntityList = noticeRepository
.findAll((root, criteriaQuery, criteriaBuilder) -> {
criteriaQuery.distinct(true);
root.fetch(NoticeEntity_.contentEntitySet, JoinType.LEFT);
Predicate restrictions = criteriaBuilder.conjunction();
SetJoin<NoticeEntity, UserNoticeEntity> recipientNoticeJoin = root
.join(NoticeEntity_.recipientNoticeEntitySet, JoinType.INNER);
recipientNoticeJoin.on(criteriaBuilder.equal(
recipientNoticeJoin.get(UserNoticeEntity_.recipientStatus), NoticeRecipientStatus.Unread));
Join<UserNoticeEntity, WeChatUserEntity> recipientUserJoin = recipientNoticeJoin
.join(UserNoticeEntity_.user);
restrictions = criteriaBuilder.and(restrictions,
criteriaBuilder.equal(recipientUserJoin.get(WeChatUserEntity_.id), id));
// recipientNoticeJoin.fetch(UserNoticeEntity_.user, JoinType.INNER);
return restrictions;
});
When I comment the code "recipientNoticeJoin.fetch(UserNoticeEntity_.user, JoinType.INNER);", it is working fine, but when I un-comment this, I will get error:
org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list
So, I am wondering if join fetch is supported by using Specification way, or there is something wrong with my code.
I know there is another way by using #Query("some hql"), but somehow I just prefer to use the Specification way.
Thanks a lot.
The error specifies that you're missing an entity from your select list. Try this:
criteriaQuery.multiselect(root, root.get(NoticeEntity_.recipientNoticeEntitySet);
Also, hibernate may run a count query first to determine the number of results, and this can cause the above error. You can avoid this breaking by checking the return type of the query before adding the fetch.
Eager fetching in a Spring Specification

Add dynamic criteria to a JPA custom Query

I have a complex query(using multiple joins and subqueries) written in HQL which I have used in a Repository class. Similar to one below -
#Repository
public interface DataRepository extends PagingAndSortingRepository<Data,String> {
public List<Data> findByService(#Param("service")Service service, Pageable page);
#Query("SELECT DISTINCT d from Data d "
+" WHERE (d.working in (SELECT d1 from Data d1 "
+" JOIN d1.working d1w "
+" JOIN d1.service s WITH (s in (:serviceList)))"
+" OR d.cleared IS NOT NULL) AND [..several other CRITERIA]")
public Page<Data> findForServices(#Param("serviceList")Set<Service> serviceList, Pageable page);
....
Now I need to add criteria to it dynamically. These criteria are flexible in number which is holding me from including it into the HQL straightaway. Is it anyhow possible?
Sifting through the internet I have come across solutions for dynamic query. But, I guess they would be working only for cases where I do not have a custom query i.e.- no #Query at the query in the repository.
There was another interesting question I found. But that also suits for a case where you have a single table to query.
I do not want to be switching over to raw SQL queries. How do I solve this?
The mentioned Criteria API with specifications and predicates is a little bit difficult to get used to but it is a good way to handle dynamic conditions.
I don't think it is possible to mix the annotation based query with programmatic query creation.

Resources