Finding Maximum query result using spring-data-jpa #Query annotation - jpa

Is is possible to limit query results using .setMaxResults(1).getResultList() and #Query - something like:
#Query("SELECT l FROM LocationLog l WHERE l.visit = :visit ORDER BY l.eventTime").setMaxResults(1).getResultList()
public LocationLog findLast(#Param("visit") Visit visit);
This code is not correct as the setMaxResults and so on is outside the #Query?l

You cannot set pagination options in #Query annotation, but it can be done in other way. You can change your method declaration to
public LocationLog find(#Param("visit") Visit visit, Pageable pageable);
Then you will be able to pass additional argument to query invocation:
Pageable firstFive = new PageRequest(0, 5);
LocationLog locLog = locationLogDao.find(visit, firstFive);
There's also option to know current context of the query results you can use Page interface changing method return type to Page<LocationLog>. You can find all necessary informations here.

Related

Spring JPA repository casting error when using JPQL

I have a PagingAndSorting JPA repository declared. I am using the #Query annotation.
I am getting an exception when I call the get() method on an Optional object from the findById(id) method of the repository.
The weird thing is it only happens when I use JPQL.
The code works if my query is native:
#Override
public BatchDto findById(String id) {
Optional<Batch> findResult = this.batchRepository.findById(id);
if (!findResult.isPresent()) return null;
Batch entity = findResult.get(); **<-------- Cast Exception Here**
BatchDto dto = this.mapper.toDto(entity, BatchDto.class);
List<BatchTransaction> transactions = entity.getTransactions();
dto.setTransactionDtos(mapper.toListDto(transactions, TransactionDto.class));
return dto;
}
Inspecting the findResult object with a breakpoint - I can see:
Optional[net.domain.data.batch#4b8bb6f]
when I have nativeQuery = true in the #Query annotation.
#Query(value = Sql.FindBatchById, nativeQuery = true)
Here is the query being used:
SELECT DISTINCT(B.batchNumber), COUNT(B.batchNumber) as TransactionCount FROM BATCH B WHERE B.batchReferenceNumber = :id GROUP BY B.batchNumber
However if I change it to JPQL and remove the nativeQuery=true attribute - the findResult is
Optional[[Ljava.lang.Object;#76e04327].
and I get a ClassCastException:
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to net.domain.data.batch
So bottom line - this works when specify nativeQuery=true and fails when I try to use JPQL.
I would prefer not to specify nativeQuery as we will eventually port this db to Oracle.
First of all the query shown below doesn't return a single Batch instance. Since there are distinct and count aggregate functions, the query will return a List of aggregates.
To be able to read that statistics you can add appropriate method into the batchRepository. Something like this:
#Query("SELECT DISTINCT(B.batchNumber) as dist, COUNT(B.batchNumber) as cnt FROM BATCH B GROUP BY B.batchNumber")
List<Map<Long, Long>> findStatistics();
and then iterate through the list.
UPD
If the id parameter exactly guarantee that will return a single record, you can change a return type to a Map
#Query("SELECT DISTINCT(B.batchNumber) as dist, COUNT(B.batchNumber) as cnt FROM BATCH B WHERE B.batchReferenceNumber = :id GROUP BY B.batchNumber")
Map<Long, Long> findStatisticsById(#Param("id") Long id);

JPA eclipselink global batch fetch?

when joining I get one select per row. Solution is batch fetch but I dont want that annotation everywhere...
http://eclipse.org/eclipselink/documentation/2.4/jpa/extensions/a_batchfetch.htm
Why do I even need this? One select per row is awful... How can I set this globally? Cheers
Maybe not the ideal solution, but you may try to use JPA hints along with Java generics:
public <T> TypedQuery<T>
createBatchQuery(String ql, Class<T> clazz, String type, String size, String relation) {
return em.createQuery(jpql, clazz)
.setHint(QueryHints.BATCH_TYPE, type)
.setHint(QueryHints.BATCH_SIZE, size)
.setHint(QueryHints.BATCH, relation);
}
The above query may then be used globally and extended with concrete query implementations according to you needs, i.e.
String jpql = "SELECT c FROM Country c WHERE c.name = :name"; // or #NamedQuery
TypedQuery<Country> q = createBatchQuery(jpql, Country.class, "JOIN", "64", "c.cities");
q.setParameter("name", "Australia");
Country c = q.getSingleResult();
Articles on this topic:
Batch fetching - optimizing object graph loading
EclipseLink/Examples/JPA/QueryOptimization

Specifying a LIMIT clause in a JPA2 repository #Query statement

I have the following MySQL statement:
"SELECT * FROM $this->tableName WHERE news_publication_id = '" + newsPublicationId + "' AND list_order > '" + listOrder +"' ORDER BY list_order LIMIT 1"
I started on the data handling path by first doing an Hibernate DAO layer.
It had the following criteria based method:
#Override
public NewsHeading findNextWithListOrder(NewsPublication newsPublication, int listOrder) {
Criteria criteria = getSession().createCriteria(getPersistentClass());
criteria.add(Restrictions.eq("newsPublication", newsPublication));
criteria.add(Restrictions.gt("listOrder", listOrder)).addOrder(Order.asc("listOrder")).setMaxResults(1);
return (NewsHeading) criteria.uniqueResult();
}
And it works just fine.
Then I tried doing a JPA2 repository layer.
It now has the following JPQL based method:
#Query("SELECT nh FROM NewsHeading nh WHERE nh.newsPublication = :newsPublication AND nh.listOrder > :listOrder ORDER BY nh.listOrder ASC LIMIT 1")
public NewsHeading findByNextListOrder(#Param("newsPublication") NewsPublication newsPublication, #Param("listOrder") int listOrder);
But it doesn't work. Because the LIMIT clause is not valid.
So I wonder what to do.
Can I still use the criteria based method of above in this JPA2 repository layer ?
I would like to avoid trusting the client to pass the limit value and prefer to hard code that value (of 1) into my layer.
Also, that statement returns only one object and so there is no pagination needed.
Any tip would be most welcomed.
Kind Regards,
Stephane Eybert
You can probably this with a PageRequest. http://docs.spring.io/spring-data/jpa/docs/1.6.2.RELEASE/reference/html/repositories.html#repositories.core-concepts
Hibernate wants you to do setMaxResults() on the Query object, but JPA doesn't interface with that directly.
Other option would be to use a #NamedNativeQuery

Limiting resultset using #Query anotations

I have a query where I want to limit the size of the resultset. As JPA does not support 'LIMIT', I have been trying to work out the correct approach. I have tried:
#Query("SELECT w FROM WardTransaction w WHERE w.visit = :visit ORDER BY w.admissionDateTime DESC").setMaxResults(1)
public WardTransaction findCurrent(#Param("visit") Visit visit);
Which is not correct. I am just looking for some guidance as to the correct syntax.
My repository code is:
#Query("SELECT w FROM WardTransaction w WHERE w.visit = :visit ORDER BY w.admissionDateTime DESC")
The above is the query method definition, to setMaxResult in your query you need to use Pageable object as it follows.
#Query("SELECT w FROM WardTransaction w WHERE w.visit = :visit ORDER BY w.admissionDateTime DESC")
public void List<Entities> findAll(Pageable pageable)
JPA repository must implement SimpleJpaRepository
Set Pageable object as it follows>
Pageable pageSpecification = PageRequest(int page, int size)
Combination for Pageable and SimpleJpaRepository is the solution.
Take a look here
If you are using EntityManager and NamedQueries, there is a method setMaxResult that apply to Query object, but it is a different story.

How to call Named Query

I wrote a named query in the entity class Voter
NamedQuery(name = "Voter.findvoter", query = "SELECT count(*) FROM Voter v WHERE v.voterID = :voterID" and where v.password= : password),
I want to call this named query and I also need to set voterID and password.
Can you help me. Thank you
I assume you've missed the # symbol on your NamedQuery annotation?
In the code, you'd call it like this:
List results = em.createNamedQuery("Voter.findvoter")
.setParameter("voterID", "blah")
.setParameter("password","blahblahblah")
.getResultList();
There are two obvious issues with your named query that would cause a problems:
It is an annotation so it should be #NamedQuery not just NamedQuery
Your query is currently:
query = "SELECT count(*) FROM Voter v WHERE v.voterID = :voterID" and where v.password= : password.
The problem is that you terminate your String after :voterID, instead of after :password and you have "where" twice and you have a space between ":" and "password". Your query should look like this:
query = "SELECT count(*) FROM Voter v WHERE v.voterID = :voterID and v.password= :password"
(I have just moved the " to the end and removed the second "where" and the space after the ":")
The common steps are (named query or otherwise)
Create a query - em has five create methods.
Set the query up with parameters if needed - the query interface has these methods.
Execute the query - the query interface has 3 execution related methods.
with the above three steps you can run any JPA query.
Actually brent is right your NameQuery should be something like this,
#NamedQuery(name = "Voter.findvoter", query = "SELECT count(*) FROM Voter v WHERE v.voterID = :voterID AND where v.password = :password")
#Entity
public class Voter implements Serializable{ ... }
and somewhere else you should try this one (which Dick has already said)
public class VoterFasade{
public List<Voter> findVoter(long id,String password){
List<Voter> results = em.createNamedQuery("Voter.findvoter")
.setParameter("voterID", id)
.setParameter("password",password)
.getResultList();
return result;
}
}
then you could use it like
#Inject
VoterFasade voterFasade;
///
long id=12;
voterFasade.findVoter(id);
should actually working.(its an uncompiled code).
you could also do it with Repository, check the link below, part Repository Listing23.Example repository
enter link description here