How to have a #Query with a possibly null parameter - spring-data-jpa

I have a #Query that gives me trouble. I'd like to get all webpages that have a given parent, even if the given parent is null.
The parent can be set but it can also not be set.
I've been trying this:
#Query("SELECT w FROM Webpage w WHERE w.parent = :parent OR (parent IS NULL AND :parent.id < '1')) ORDER BY w.listOrder")
public Page<Webpage> findByParent(#Param("parent") Webpage parent, Pageable page);
And this:
#Query("SELECT w FROM Webpage w WHERE w.parent = :parent OR (parent IS NULL AND :parent IS NULL)) ORDER BY w.listOrder")
public Page<Webpage> findByParent(#Param("parent") Webpage parent, Pageable page);
But I get the exception:
Caused by: java.lang.IllegalArgumentException: Validation failed for query for method public abstract
com.thalasoft.learnintouch.data.jpa.domain.Webpage
com.thalasoft.learnintouch.data.jpa.repository.WebpageRepository.findByParentAndNameAndNotGarbage(com.thalasoft.learnintouch.data.jpa.domain.Webpage,java.lang.String)!
I'm using the spring 3.2.9.RELEASE and spring-data-jpa 1.6.1.RELEASE version.
Any idea on how I can select on a given parent, even if the given parent is null ?
Kind Regards,
Stephane Eybert

If the validation on the provider will not let you pass nulls, you can split the query into two. You'll have to have logic in the caller to figure out which one to call.
Query1 (param is not null):
#Query("SELECT w FROM Webpage w WHERE w.parent = :parent ORDER BY w.listOrder")
public Page<Webpage> findByParent(#Param("parent") Webpage parent, Pageable page);
Query2 (param is null):
#Query("SELECT w FROM Webpage w WHERE w.parent is null ORDER BY w.listOrder")
public Page<Webpage> findByNullParent(Pageable page);
I would recommend reading some more of the docs on your provider to see how to pass nulls in.

Related

How to find top N elements in Spring Data Jpa?

In Spring Data Jpa to get first 10 rows I can do this findTop10By...(). In my case the number or rows is not defined and comes as a parameter.
Is there something like findTopNBy...(int countOfRowsToGet)?
Here is another way without native query. I added Pageable as a parameter to the method in the interface.
findAllBySomeField(..., Pageable pageable)
I call it like this:
findAllBySomeField(..., PageRequest.of(0, limit)) // get first N rows
findAllBySomeField(..., Pageable.unpaged()) // get all rows
I don't know of a way to do exactly what you want, but if you are open to using #Query in your JPA repository class, then a prepared statement is one alternative:
#Query("SELECT * FROM Entity e ORDER BY e.id LIMIT :limit", nativeQuery=true)
Entity getEntitiesByLimit(#Param("limit") int limit);
Did it by using pagination, as described in the first answer. Just adding a more explicit example.
This example will give you the first 50 records ordered by id.
Repository:
#Repository
public interface MyRepository extends JpaRepository<MyEntity, String> {
Page<MyEntity> findAll(Pageable pageable);
}
Service:
#Service
public class MyDataService {
#Autowired
MyRepository myRepository;
private static final int LIMIT = 50;
public Optional<List<MyEntity>> getAllLimited() {
Page<MyEntity> page = myRepository.findAll(PageRequest.of(0, LIMIT, Sort.by(Sort.Order.asc("id"))));
return Optional.of(page.getContent());
}
}
Found the original idea here:
https://itqna.net/questions/16074/spring-data-jpa-does-not-recognize-sql-limit-command
(which will also link to another SO question btw)

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 2.0 IllegalArgumentException on existing entities and populated DB value

I am trying to return a string from a table based on a conditional ID (subid) from an already populated table. The query should return a list of type ItemDataPoint entity. In a JSF managed bean, the list will the be iterated by a an enhaned for loop. If the word "Include" is found by the loop, the method will create a specific type of chart. In simpler terms, I want to return a string based the ID condition being met. I am getting:
javax.ejb.EJBException
at com.sun.ejb.containers.EJBContainerTransactionManager.processSystemException
(EJBContainerTransactionManager.java:748)
at com.sun.ejb.containers.EJBContainerTransactionManager.
completeNewTx(EJBContainerTransactionManager.java:698)
at com.sun.ejb.containers.EJBContainerTransactionManager.postInvokeTx
(EJBContainerTransactionManager.java:503)
at com.sun.ejb.containers.BaseContainer.postInvokeTx(BaseContainer.java:4475)
at com.sun.ejb.containers.BaseContainer.postInvoke(BaseContainer.java:2009)
at com.sun.ejb.containers.BaseContainer.postInvoke(BaseContainer.java:1979)
Caused by: java.lang.IllegalArgumentException: You have attempted to set
a parameter at position 2 which does not exist in this query string SELECT p FROM
Itemdatapoint p JOIN p.series s WHERE s.master.item.subs.subid = :subid.
at org.eclipse.persistence.internal.jpa.QueryImpl.setParameterInternal(QueryImpl.java:925)
at org.eclipse.persistence.internal.jpa.QueryImpl.setParameterInternal(QueryImpl.java:906)
at org.eclipse.persistence.internal.jpa.EJBQueryImpl.setParameter(EJBQueryImpl.java:469)
at org.eclipse.persistence.internal.jpa.EJBQueryImpl.setParameter(EJBQueryImpl.java:1)
at com.manaar.clientmods.gazprom.design3.data.facade.ItemdatapointFacade.
chartType(ItemdatapointFacade.java:78)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
The subid value of 2 exists if I run a native SQL query on the relevant parent table in the DB. Also the type of the subid is an int in the main entity class, the JPQL Facade class and the managed bean.
The JPQL:
public List<Itemdatapoint> chartType (int subid) {
Query q = em.createQuery("SELECT p FROM Itemdatapoint p JOIN p.series s WHERE s.master.item.subs.subid = :subid");
q.setParameter(subid, "subid");
return q.getResultList();
}
The managed bean:
#Named(value = "reportBean")
#SessionScoped
public class ReportBean implements Serializable {
#Inject
private ItemdatapointFacade facade;
public String typeSwitch1() {
subid = 2;
chartType = facade.chartType(subid);
for(Itemdatapoint e: chartType) {
status = e.getSeries().getMaster().getStatus();
if(status.equals("Include")) {
return "line";
}
}
return null;
}
The xhtml page:
<p:chart type="#{reportBean.typeSwitch1()}" model="#{reportBean.subLineChart1}"/>
I also tried a non Join JPQL just from a single table:
public List<Itemdatapoint> noJoin (int subid) {
Query q = em.createQuery("SELECT p FROM Itemdatapoint p WHERE p.pointid = :subid");
q.setParameter(subid, "subid");
return q.getResultList();
}
Similar problem:
java.lang.IllegalArgumentException: You have attempted to set a
parameter at position 2 which does not exist in this query string
SELECT p FROM Itemdatapoint p WHERE p.pointid = :subid.
I gather that IllegalArgumentException means that the selected entity does not exist or is not the correct type consistent with the query string in the facade class. But in my case the entity exists and the parameter is the correct type.
I would appreciate any help in understanding why i'm getting this error. Thank in advance!
UPDATE
Responding to the answer from lametaweb, I want to better understand the concept of JPA parameters.
According to the JPA documentation, the first argument of the setParameter method is the parameter name or number. The second argument is the object that should be bound to the named parameter. Why does the following work without throwing Illegal ArgumentException?
I tested an xhtml (web page):
<p:dataGrid id="rep1" columns="1" value="#{pageBean.itemPageList1}" var="items1" rows="4">
<p:commandLink value="#{items1.itemname}" action="#{pageBean.showItem1}" ajax="false"/>
</p:dataGrid>
The bean code:
public ListDataModel<Sectionitem> getItemPageList1() {
subid = 1;
reportStatus = "Include";
itemPageList1 = itemFacade.viewItems(subid, reportStatus);
return itemPageList1;
}
The JPA facade:
public ListDataModel<Sectionitem> viewItems(int subid, String stat) {
Query q = em.createQuery("select s from Sectionitem s JOIN s.subs c where c.subid = :subid AND s.status = :stat ORDER BY s.daterec");
q.setParameter("subid", subid);
q.setParameter("stat", stat);
ListDataModel<Sectionitem> res
= new ListDataModel<Sectionitem>(q.getResultList());
return res;
}
Why is it in this case, the object exist but in my original case the subid object does not exist?
You are invoking this method in your code:
setParameter(int position, Object value)
but you have to invoke this one instead:
setParameter(String name, Object value)
So your code should be:
q.setParameter("subid", Integer.valueOf(subid));
But, if you invoke:
q.setParameter(subid, "subid");
here the first parameter represents the position of the argument and the second the value for it. So you are passing a value of "subid" for the parameter in the second (2) position, which doesn't exist, because you only have one parameter in your JPQL query, hence the IllegalArgumentException exception.
Note: Why do you have a primitive type in your entity? Why not an Integer instead an int?

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

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.

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.