JPA native query with same column twice - spring-boot

I am a JPA newbie and wanted to have a JPA native query for a single table (below) which I would like to fetch in my #Entity based class called TestRequest. It has a column 'RequestTime' that is fetched with DAYNAME() and then with DATEDIFF() functions.
SELECT TestRequest.Id AS Id
, TestRequest.RequestTime AS RequestTime
, DAYNAME(TestRequest.RequestTime) AS RequestDay
, TestRequest.StatusMessage AS StatusMessage
, DATEDIFF(CURDATE(), TestRequest.RequestTime) AS HowLongAgo
FROM TestRequest
LEFT JOIN TestRun
ON TestRequest.TestRunId = TestRun.Id
WHERE Requestor = '[NAME]'
ORDER BY Id DESC
Is there any way in which the column (fetched second time as HowLongAgo) be set into a property which is not mapped to a table column within the TestRequest class? Are there any field level annotations for this?

You need to use Interface-based projections:
You will need to create an interface that define the getters for each field in your projection like:
public interface RequestJoinRunProjection {
int getId();
LocalDate getRequestTime();
String getMessage();
String getRequestDay();
Long getHowLongAgo();
}
Then you define a method on your Repository that has the native query you want to run:
public interface TestRequestRepository extends CrudRepository<TestRequest, Long> {
// Any other custom method for TestRequest entity
#Query(value = "SELECT trq.Id AS id " +
" , trq.RequestTime AS requestTime " +
" , DAYNAME(trq.RequestTime) AS requestDay " +
" , trq.StatusMessage AS statusMessage " +
" , DATEDIFF(YEAR, CURDATE(), trq.RequestTime) AS howLongAgo " +
"FROM TestRequest trq " +
" LEFT JOIN TestRun tr " +
" ON trq.TestRunId = tr.Id " +
"WHERE Requestor = ?1 ORDER BY Id DESC"), nativeQuery = true)
List<RequestJoinRunProjection> findTestSumary(String name);
}
Notice query must be native since you are using database functions, also the column names must match the setters of your projection interface(following bean rules), so use AS in order to change the names in your query.
I strongly suggest you test your query on h2 before injecting into #Query annotation. DATEDIFF requires 3 parameters.

Related

Spring JPA projection - selecting specific columns in nested object list

Why I get the following error:
query specified join fetching, but the owner of the fetched
association was not present in the select list
when I try to get List of ids?
#Query("SELECT at.id FROM Template at " +
"WHERE at.shipper.id = :companyId " +
"AND at.isActive = true")
#EntityGraph(attributePaths = {"shipper"})
List<Long> findTemplateIdsByCompanyId2(Long companyId, Pageable pageable);
but when I want to get list of objects - everything is OK?
#Query("SELECT at FROM Template at " +
"WHERE at.shipper.id = :companyId " +
"AND at.isActive = true")
#EntityGraph(attributePaths = {"shipper"})
List<Template > findTemplateIdsByCompanyId2(Long companyId, Pageable pageable);
Template entity has OneToOne relationship with shipper field and OneToMany relationship with warehouse field
You need to join in the query if you are not fetching the entity, something like this should do it:
SELECT at.id FROM Template at JOIN at.Shipper s WHERE s.id = :companyId and at.isActive = true

search collection by index in JPA #query

I have 2 entities with OneToMany Relation as below,
public class Visit{
#OneToMany(mappedBy = "visit", fetch = FetchType.LAZY)
#OrderBy("updated_on DESC")
private List<StatusChange> statusHistory;
}
public class StatusChange{
#ManyToOne
#JoinColumn(name = "ms_visit_id")
private Visit visit;
#Column(name = "to_status")
#Enumerated(EnumType.STRING)
private VisitStatus toStatus;
}
Visits can have multiple status like created, canceled, deleted etc.. whenever the status change there will be a new row added to the StatusChange table with a entry associated to that visit (toStatus will become canceled) . Now I want a query to filter the visits for which the latest status is canceled and with the matching user id.
I am using the #query of JPA. I already have got the result with the following query.
#Query(value = "select vs1.visit from StatusChange vs1 where vs1.id in (" +
" select max(vs2.id) from StatusChange vs2 " +
" where vs2.visit.user.id = :userId" +
" group by vs2.visit.id)" +
" and vs1.toStatus in :status")
public List<MSVisit> findByUserAndStatus(#Param("userId") Long userId, #Param("status") List<Visit.VisitStatus> status);
But I feel the query can be improved or is there any way to query some thing like,
"from Visit visit" +
" where visit.statusHistory.get(0).toStatus in :status" +
" and visit.user.id = :userId
Thanks for your help.
Usually, these queries are best modeled with lateral joins, but Hibernate does not support that yet. Note that unless you meant something different, your query is not correct, as the max-aggregation is based on the value of the id rather than the updated_on value. Not sure if that is on purpose, but I would suggest the following query in case you really want the latest visit.
#Query(value = "from Visit vs1 where vs1.user.id = :userId and (select max(h.updatedOn) from vs1.statusHistory h) >= ALL (" +
" select max(h.updatedOn) from vs1.statusHistory h " +
" and vs1.toStatus in :status")
public List<MSVisit> findByUserAndStatus(#Param("userId") Long userId, #Param("status") List<Visit.VisitStatus> status);

How to use Spring boot JPA native query with join unrelated entities

I have a custom model for order and item that will hold minimum data than the actual entity class
Models
class OrderMinimalModel {   
long id;   
String comment;   
List<ItemMinimalModel> items;
}
class ItemMinimalModel{   
long id;   
String name;
}
Query I am looking for
#Query( value = "SELECT O.id as orderId, O.comment as orderComment, I.id as itemId, I.name as itemName FROM order O "
+ " left join item I on I.order_id = O.id"
+ " WHERE O.status = 1 ",nativeQuery = true)
List<OrderMinimalModel > findAllOrderMinimal();
But I am getting bellow error
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap] to type [com.example.model.OrderMinimalModel]
Maybe I am doing wrong.

spring data jpa custom query fails to recognize class type

Not able to use custom POJO classes for my spring data jpa queries. Repeatedly fails with the following exception
"org.hibernate.MappingException: Unknown entity:
com.app.mycompany.AgileCenterServices.entities.ComponentDetailedInfo"*
Tried replacing the custom ComponentDetailedInfo.class and not mentioning anything during the call to entityManager.createNativeQuery(componentQuery.toString()), but then Object List returned fails to be converted to the specific POJO class after the query.
#Override
public ComponentListResponsePaginated findComponentByProjectId(String projectId, Pageable pageable) {
logger.info(" Inside findComponentByProjectId() API in IssueComponentServiceImpl");
String componentQuery = "select c.*, u.fullname "
+ "from issue_component c "
+ "left join user u on c.component_lead = u.username "
+ "where "
+ "upper(c.project_id) = upper(" + projectId + ")";
List<ComponentDetailedInfo> compList = new ArrayList<ComponentDetailedInfo>();
try {
logger.info(" ************* Printing query ******************************* ");
logger.info(componentQuery.toString());
compList = entityManager.createNativeQuery(componentQuery.toString(), ComponentDetailedInfo.class) .setFirstResult(pageable.getOffset())
.setMaxResults(pageable.getPageSize())
.getResultList();
}
}
Also tried the following
List<? extends Object> objList = null;
objList = entityManager.createNativeQuery(componentQuery.toString()) .setFirstResult(pageable.getOffset())
.setMaxResults(pageable.getPageSize())
.getResultList();
if(objList != null && objList.size() > 0) {
for(Object rec: objList) {
logger.info(" Printing Object ::: " + rec.toString());
compList.add((ComponentDetailedInfo)rec);
}
}
However the compList fails with the
java.lang.ClassCastException
The custom query returned should get typecast to the specific class type passed to the entityManager.createNativeQuery. However, I am facing the exception as mentioned above when I pass the class to createNativeQuery().
Even tried by totally removed the class in the createNativeQuery...
You have to define a constructor result mapping if you want to use a POJO as a result of a native query.
Here is an example query:
Query q = em.createNativeQuery(
"SELECT c.id, c.name, COUNT(o) as orderCount, AVG(o.price) AS avgOrder " +
"FROM Customer c " +
"JOIN Orders o ON o.cid = c.id " +
"GROUP BY c.id, c.name",
"CustomerDetailsResult");
And that's the mapping you have to add to your Entity:
#SqlResultSetMapping(name="CustomerDetailsResult",
classes={
#ConstructorResult(targetClass=com.acme.CustomerDetails.class,
columns={
#ColumnResult(name="id"),
#ColumnResult(name="name"),
#ColumnResult(name="orderCount"),
#ColumnResult(name="avgOrder", type=Double.class)})
})
If you don't like that approach you could use QLRM. Learn more about it here: https://github.com/simasch/qlrm

Spring Data - Projection and #Query nested property

Assume I have those DTO:
public interface ForumDTO extends ForumBaseDTO{
Integer getId();
ThreadDTO getLastThread();
}
public interface ThreadDTO {
Integer getId();
Integer getCommentCount()
}
In my Repository I have this query using those DTO as projection:
#Query("select forum.id as id, " +
"forum.name as name, " +
"lastThread.id as lastThread_id " +
"from Forum forum " +
"inner join forum.lastThread as lastThread " +
"where forum.parent.id = ?:"
)
Iterable<ForumDTO> findAllByParentId(Integer id);
I can access id,name in ForumDTO using this repo just fine, but with lastThread it just return null. I have tried as lastThread.Id,as lastThread_id, as lastThreadId but none of them work.
You're almost there.
You need to access it from forum to follow out the foreign key:
#Query("select forum.id as id, " +
"forum.name as name, " +
"**forum.lastThread.id** as lastThread_id " +
"from Forum forum " +
"inner join forum.lastThread as lastThread " +
"where forum.parent.id = ?:"
)
Iterable<ForumDTO> findAllByParentId(Integer id);
That said, you're killing yourself with extra work.
The same Query can be written as:
#Query("select forum from Forum where forum.parent.id = :forumId")
Iterable<ForumDTO> findAllByParentId(#Param("forumId")Integer id);
You just need to make sure that the foreign key to Parent is present on the entity.
Also notice the #Param annotation. It makes your parameters easier to track, and also does some basic type checking against the db. It's very useful to prevent SQL injection attacks, and UncheckedTypeExceptions.

Resources