Spring Data JPA findAllBy ... in ... orderBy input list - java

I'm using JPA with Spring Boot in Kotlin/Java. I'm trying to find the proper and efficient way to do findBy ... In OrderBy input.
I got a list of the Ids I want to find and I want an ordered output with the same order. This is what JPA allows you:
#Repository
interface PhotoRepository : JpaRepository<Photo, String>{
// Which is the same as this query
#Query("SELECT p FROM Photo p where p.id in :var1")
fun findAllByIdIn(var1: List<String>, pageable: Pageable): List<Photo>
}
Would be great if JPA allows you to do something like this:
#Repository
interface PhotoRepository : JpaRepository<Photo, String>{
#Query("SELECT p FROM Photo p where p.id in :var1 order by :var1")
fun findAllByIdInOrderByvar1(var1: List<String>, pageable: Pageable): List<Photo>
}
The size of the id list is between 500- 1500 items. There are lots of records in the database and the idea of select all the records is not feasible
The contemplated solution is to do a findAllByIdIn and then match the records with the ids in list, which I think is not the proper solution, there are extra operations. The idea of changing the database is also contemplated.

Once you get all the items in a certain id, with findAllByIdIn, to get the correct order, I used the following method:
private fun getOrderedItemsFromOneListToAnother(photosOrder: List<String>,
photosFound: List<Photo>): List<Photo>{
val mapStringIdsOrder = photosOrder.mapIndexed { index, s -> s to index}.toMap()
Collections.sort(photosFound, Comparator { o1: Photo, o2: Photo->
Integer.compare(mapStringIdsOrder.getOrDefault(o1.id,photosOrder.size),
mapStringIdsOrder.getOrDefault(o2.id, photosOrder.size))})
return photosOrder
}

Related

In back end (java spring boot) sorting, how to sort with Alias name using pageable? Without parent table as prefix

I am using backend pagination and sorting with java spring boot pageable. While passing sort field as usercount (This gives count of user_role_mapping), Java triggering an error column f.usercount does not exist .
Actually usercount not a column it's an Alias name.
How to sort using usercount as Alies name without f. as prefix?
API URL:
http://localhost:8080/facility/list?pageNumber=0&pageSize=10&sortBy=usercount&sortType=asc
Default sortBy & sortType are id and desc respectively in controller layer.
Java Code give below:
Pageable pageable = PageRequest.of(pageNumber, pageSize, Sort.by(sortBy).descending());
if (sortType.equalsIgnoreCase("asc")) {
pageable = PageRequest.of(pageNumber, pageSize, Sort.by(sortBy).ascending());
}
Page<FacilityProjection> facilityList = facilityRepository.facilityListing(12,pageable);
Postgres sql Hibernate query for listing facility details along with user count based on role id, given below:
#Query(nativeQuery = true, value = " Select f.name as facilityname,f.id as facilityid,count(urm.id) as usercount
from facility f
join user u on f.user_id=u.id
join user_role_mapping urm on u.id = urm.user_id
where urm.role_id=:roleId ")
Page<FacilityProjection> facilityListing(#Param("roleId") Long roleId,Pageable pageable);
The problem is that usercount is aggregation function result. To order by this field query have to contain order by count(urm.id) instead of order by usercount.
In this case I'd suggest you to resort page content using Collections::sort
boolean sortByUserCount = sortBy.equalsIgnoreCase("usercount");
boolean desc = sortType.equalsIgnoreCase("desc");
final Pageable pageable;
if (sortByUserCount) {
pageable = PageRequest.of(pageNumber, pageSize);
} else {
if (desc) {
pageable = PageRequest.of(pageNumber, pageSize, Sort.by(sortBy).descending());
} else {
pageable = PageRequest.of(pageNumber, pageSize, Sort.by(sortBy).ascending());
}
}
Page<FacilityProjection> facilityList = facilityRepository.facilityListing(12, pageable);
if (sortByUserCount) {
Comparator<FacilityProjection> comparator = Comparator.comparing(FacilityProjection::getUserCount);
if(desc) {
comparator = comparator.reversed();
}
Collections.sort(facilityList.getContent(), comparator);
}
As far as I know, this is not possible.
Having said that, I think this is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(Facility.class)
public interface FacilityProjection {
#IdMapping
Long getId();
String getName();
#Mapping("SIZE(users)")
Long getUsercount();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
FacilityProjection a = entityViewManager.find(entityManager, FacilityProjection.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features so you can see this as a replacement for Spring Data Projections that supports more use cases!

What is the LIMIT clause alternative in JPQL?

I'm working with PostgreSQL query implementing in JPQL.
This is a sample native psql query which works fine,
SELECT * FROM students ORDER BY id DESC LIMIT 1;
The same query in JPQL doesnt work,
#Query("SELECT s FROM Students s ORDER BY s.id DESC LIMIT 1")
Students getLastStudentDetails();
seems like LIMIT clause doesn't work in JPQL.
According to JPA documentation we can use setMaxResults/setFirstResult, Can anyone tell me how can I use that in my above query?
You are using JPQL which doesn't support limiting results like this. When using native JPQL you should use setMaxResults to limit the results.
However you are using Spring Data JPA which basically makes it pretty easy to do. See here in the reference guide on how to limit results based on a query. In your case the following, find method would do exactly what you want.
findFirstByOrderById();
You could also use a Pageable argument with your query instead of a LIMIT clause.
#Query("SELECT s FROM Students s ORDER BY s.id DESC")
List<Students> getLastStudentDetails(Pageable pageable);
Then in your calling code do something like this (as explained here in the reference guide).
getLastStudentDetails(PageRequest.of(0,1));
Both should yield the same result, without needing to resort to plain SQL.
As stated in the comments, JPQL does not support the LIMIT keyword.
You can achieve that using the setMaxResults but if what you want is just a single item, then use the getSingleResult - it throws an exception if no item is found.
So, your query would be something like:
TypedQuery<Student> query = entityManager.createQuery("SELECT s FROM Students s ORDER BY s.id DESC", Student.class);
query.setMaxResults(1);
If you want to set a specific start offset, use query.setFirstResult(initPosition); too
Hello for fetching single row and using LIMIT in jpql we can tell the jpql if it's a native query.
( using - nativeQuery=true )
Below is the use
#Query("SELECT s FROM Students s ORDER BY s.id DESC LIMIT 1", nativeQuery=true)
Students getLastStudentDetails();
You can not use Limit in HQL because Limit is database vendor dependent so Hibernate doesn't allow it through HQL query.
A way you can implement is using a subquery:
#Query("FROM Students st WHERE st.id = (SELECT max(s.id) FROM Students s)")
Students getLastStudentDetails();
The correct way is to write your JPA interface method like this
public interface MyRepository extends PagingAndSortingRepository<EntityClass, KeyClass> {
List<EntityClass> findTop100ByOrderByLastModifiedDesc();
}
In the method name, "100" denotes how many rows you want which you would have otherwise put in the limit clause. also "LastModified" is the column which you want to sort by.
PagingAndSortingRepository or CrudRepository, both will work for this.
For the sake of completeness, OP's interface method would be
List<Students> findTop1ByIdDesc();
JPQL does not allow to add the limit keyword to the query generated by the HQL. You would get the following exception.
org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token:
LIMIT near line 1
But don't worry there is an alternative to use the limit keyword in the query generated by the HQL by using the following steps.
Sort.by(sortBy).descending() // fetch the records in descending order
pageSize = 1 // fetch the first record from the descending order result set.
Refer the following service class
Service:
#Autowired
StudentRepository repository;
public List<Student> getLastStudentDetails(Integer pageNo, Integer pageSize, String sortBy)
{
Integer pageNo = 0;
Integer pageSize = 1;
String sortBy = "id";
Pageable paging = PageRequest.of(pageNo, pageSize, Sort.by(sortBy).descending());
Slice<Student> pagedResult = repository.findLastStudent(paging);
return pagedResult.getContent();
}
Your repository interface should implement the PagingAndSortingRepository
Repository:
public interface StudentRepository extends JpaRepository<Student,Long>, PagingAndSortingRepository<Student,Long>{
#Query("select student from Student student")
Slice<Student> findLastStudent(Pageable paging);
}
This will add the limit keyword to you query which you can see in the console. Hope this helps.
Hardcode the pagination(new PageRequest(0, 1)) to achieve fetch only one record.
#QueryHints({ #QueryHint(name = "org.hibernate.cacheable", value = "true") })
#Query("select * from a_table order by a_table_column desc")
List<String> getStringValue(Pageable pageable);
you have to pass new PageRequest(0, 1)to fetch records and from the list fetch the first record.
Here a Top Ten Service (it's a useful example)
REPOSITORY
(In the Query, I parse the score entity to ScoreTo ( DTO class) by a constructor)
#Repository
public interface ScoreRepository extends JpaRepository<Scores, UUID> {
#Query("SELECT new com.example.parameters.model.to.ScoreTo(u.scoreId , u.level, u.userEmail, u.scoreLearningPoints, u.scoreExperiencePoints, u.scoreCommunityPoints, u.scoreTeamworkPoints, u.scoreCommunicationPoints, u.scoreTotalPoints) FROM Scores u "+
"order by u.scoreTotalPoints desc")
List<ScoreTo> findTopScore(Pageable pageable);
}
SERVICE
#Service
public class ScoreService {
#Autowired
private ScoreRepository scoreRepository;
public List<ScoreTo> getTopScores(){
return scoreRepository.findTopScore(PageRequest.of(0,10));
}
}
You can use something like this:
#Repository
public interface ICustomerMasterRepository extends CrudRepository<CustomerMaster, String>
{
#Query(value = "SELECT max(c.customer_id) FROM CustomerMaster c ")
public String getMaxId();
}
As your query is simple, you can use the solution of the accepted answer, naming your query findFirstByOrderById();
But if your query is more complicated, I also found this way without need to use a native query:
#Query("SELECT MAX(s) FROM Students s ORDER BY s.id DESC")
Students getLastStudentDetails();
Here a practical example where the named query method cannot be used.

Spring JPA Specification with Sort

I am using Spring JPA.
To be more precise I am using a Repository which extends JpaRepository and JpaSpecificationExecutor because I require pagination, filtering and sorting.
Now I have the pagination and filtering all working just fine, but I cannot get sorting to work as well.
I notice with some disappointment that JpaSpecificationExecutor has findAll() methods:
findAll(Specification, Pageable);
findAll(Specification, Sort);
But the one I need:
findAll(Specification, Pageable, Sort); //or something like this
does not exist!
So, Plan B, include Sorting in the Specification.
With the help of the Accepted Answer to this question: JpaSpecificationExecutor JOIN + ORDER BY in Specification I put together the following:
private Specification<MainEntity> matches() {
return new Specification<MainEntity>() {
public Predicate toPredicate(Root<MainEntity> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
List<Predicate> predicates = new ArrayList<Predicate>();
//Attempt to sort by Surname, has no effect
query.orderBy(cb.asc(root.get("surname")));
//add string filters
for (String field : stringFilterMap.keySet()) {
String valuePattern = stringFilterMap.get(field);
predicates.add(cb.like(root.get(field), "%"+valuePattern+"%"));
}
//...snip...
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
};
}
Where springFilterMap is an instance field, Map<String,String> whose keys are field names and values are filters values.
Above you will see my attempt to order by Surname, but this seems to have no effect.
What am I doing wrong; & how can I achieve Sorting along with Pagination and Filtering?
Use PageRequest, which is an implementation of Pageable, and implements paging and sorting at once, like you want it. For example through this constructor:
public PageRequest(int page, int size, Sort sort)
UPDATE:
Since Spring Data JPA 2.0 the above constructor is deprecated and you should use the static factory method of:
public static PageRequest of(int page, int size, Sort sort)

Criteria Api: Order/Select by implicit related table

I try to build a CriteriaQuery which provides the following functionality:
I have three tables with the following fields:
table_a:
id, name_a
table_b:
id, name_b
table_ab:
id_a, id_b
Now I want to get all elements out of table_a ordered by the name_b field of the corresponding element in table_b.
The Result should be a Specification for usage in a JpaRepository. I tried using joins, but i stuck at the point, how to combine the joins:
Specification<TableA> specification = (root, query, cb) -> {
CriteriaQuery<TableAb> abQuery = cb.createQuery(TableAb.class);
CriteriaQuery<TableB> bQuery = cb.createQuery(TableB.class);
Root<TableAb> abRoot = abQuery.from(TableAb.class);
Join<TableAb, TableA> aJoin = abRoot.join("tableA");
Join<TableAb, TableB> bJoin = abRoot.join("tableB");
//combine joins
query.orderBy(cb.asc(/* Expression to order by */));
return cb.conjunction();
};
In my opinion the main problem is that there is no "path" from table_a to table_b, but I explicitly do not want to have any reference inside of table_a to table_b.
Since you're using Spring Data JPA , you can just make an interface with a method on it that look like this:
public interface TableABRepository extends Repository<TableAB, Long> {
public List<TableAB> findAllByOrderByTableB();
}
Assuming your TableAB class is something like this:
class TableAB {
TableA tableA;
TableB tableB;
}
Thak method will return all elements from table_ab ordered by the name_b field.
After that you just get the TableA elements from the TableAB returned list.

Spring Data Mongo - Query methods and Distinct field

I'm currently working on a project using Spring Data Mongo.
My repository is just an interface extending MongoRepository. I would like to add a custom query method in order to retrieve all distinct values for one of my collection's fields.
I tried something like this:
#RepositoryRestResource(path = "devices", collectionResourceRel = "deviceInfos")
public interface DeviceInfoRepository extends MongoRepository<DeviceInfo, String> {
#RestResource(path = "distinctUnitIds")
List<String> findDistinctUnitIdBy();
}
With that code, Spring give me an error because it's not able to build my list. So I tried this:
#RepositoryRestResource(path = "devices", collectionResourceRel = "deviceInfos")
public interface DeviceInfoRepository extends MongoRepository<DeviceInfo, String> {
#RestResource(path = "distinctUnitIds")
List<DeviceInfo> findDistinctUnitIdBy();
}
That code works but the distinct seems to be totally ignored.
The documentation about Distinct in query method is really not clear...
Did I do something wrong? What's the best way to solve get the distinct values of a field using Spring Data?
Thanks!
You will have to use Spring Data MongoTemplate - the MongoRepository interfaces are made only for basic functionality and for more fine grain control of what you are querying, its best to use MongoTemplate.
Here is an example of how one would get distinct values from a collection:
Criteria criteria = new Criteria();
criteria.where("dataset").is("d1");
Query query = new Query();
query.addCriteria(criteria);
List list = mongoTemplate.getCollection("collectionName")
.distinct("source",query.getQueryObject());
Here is the link to more info: mongodb mongoTemplate get distinct field with some criteria
in SpringBoot2 you can do the following :
DistinctIterable<String> iterable = mongoTemplate.getCollection(COLLECTION_NAME).distinct("source",in(FieldValue,query.getQueryObject(), String.class);
MongoCursor<String> cursor = iterable.iterator();
List<String> list = new ArrayList<>();
while (cursor.hasNext()) {
list.add(cursor.next());
}
return list;

Categories