how to use SpringData findAll() between select when using an object as major condition? - spring

So there's a method in SpringData findAll(Pageable pageable,Condition condition) ;,
usually I use it like findAll(pageable,myobject) .
The question is when it comes to select some records between some certain field range ,like select out objects whose createDate are between A and B , how to use findAll()?
I tried findAllByCreateDateAfterAndCreateDateBefore(Pageable pageable,Date a,Date b);
But here I can't put myObj as a condtion into the method , and it caused a lot trouble when some fields in the myObj are not sure if it would be used as a condition.

You can simply use JPA Specification to do this, then :
fun findAll(spec: Specification<YourObject>, pageable: Pageable): Page<YourObject>
Here simple example how it should be use :
JPA Specification example

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 Data JPA #Query with Specification

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

How to pass column name dynamically inside a #Query annotation using Spring data JPA

I have entity like:
#Id
#Column_name = "abc"
int pk;
#Column_name = "def"
int id;
And I have Repository as:
interface fetchDataRepository extends jpaRepository<className, int> {
#Query("Select S_Test.nextVal from dual");
Long generateId();
}
In above example S_Test is hardcoded sequence name.
But the problem is that I want to pass sequence name dynamically as follows:
Long generateId(#Param("sequenceName") String sequenceName)
and use inside #Query annotation as:
#Query("Select :sequenceName.nextVal from dual");
Is there anyway to do that? Any suggestion would be appreciated.
Edit: Isn't there possible to use #(#entityName). If yes, then please tell me how?
Unfortunately you can only substitute in things that you could do in JDBC anyway (so, pretty much just values in the INSERT and WHERE clauses). No dynamic table, column, schema names are supported.
There is one exception that may apply, and that is a limited subset of SpEL can be used. There is one variable available - #entityName. So, assuming that the #Entity annotation on your entity class is named identically to the sequence, you could use an #Query like so:
#Query("Select #{#entityName}.nextVal from dual");
Otherwise, since your query is simple and does not involve any object relational mapping, you would probably need to Create a custom repository implementation and inject a JdbcTemplate into it in order to run the query.
Else you could inject an EntityManager and try using the JPA Criteria API - but again you arent actualy trying to map a resultset to an entity so JdbcTemplate will be simpler.

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.

SimpleJpaRepository Count Query

I've modified an existing RESTful/JDBC application i have to work with new features in Spring 4... specifically the JpaRepository. It will:
1) Retrieve a list of transactions for a specified date. This works fine
2) Retrieve a count of transactions by type for a specified date. This is not working as expected.
The queries are setup similarly, but the actual return types are very different.
I have POJOs for each query
My transactions JPA respository looks like:
public interface MyTransactionsRepository extends JpaRepository<MyTransactions, Long>
//My query works like a charm.
#Query( value = "SELECT * from ACTIVITI_TMP.BATCH_TABLE WHERE TO_CHAR(last_action, 'YYYY-MM-DD') = ?1", nativeQuery = true )
List< MyTransactions > findAllBy_ToChar_LastAction( String lastActionDateString );
This returns a list of MyTransactions objects as expected. Debugging, i see the returned object as ArrayList. Looking inside the elementData, I see that each object is, as expected, a MyTransactions object.
My second repository/query is where i'm having troubles.
public interface MyCountsRepository extends JpaRepository<MyCounts, Long>
#Query( value = "SELECT send_method, COUNT(*) AS counter FROM ACTIVITI_TMP.BATCH_TABLE WHERE TO_CHAR(last_action, 'YYYY-MM-DD') = ?1 GROUP BY send_method ORDER BY send_method", nativeQuery = true )
List<MyCounts> countBy_ToChar_LastAction( String lastActionDateString );
This DOES NOT return List as expected.
The object that holds the returned data was originally defined as List, but when I inspect this object in Eclipse, I see instead that it is holding an ArrayList. Drilling down to the elementData, each object is actually an Object[2]... NOT a MyCounts object.
I've modified the MyCountsRepository query as follows
ArrayList<Object[]> countBy_ToChar_LastAction( String lastActionDateString );
Then, inside my controller class, I create a MyCounts object for each element in List and then return List
This works, but... I don't understand why i have to go thru all this?
I can query a view as easily as a table.
Why doesn't JPA/Hibernate treat this as a simple 2 column table? send_method varchar(x) and count (int or long)
I know there are issues or nuances for how JPA treats queries with counts in them, but i've not seen anything like this referenced.
Many thanks for any help you can provide in clarifying this issue.
Anthony
That is the expected behaviour when you're doing a "group by". It will not map to a specific entity. Only way this might work is if you had a view in your database that summarized the data by send_method and you could map an entity to it.

Resources