How to iterate from the JPA query in spring boot based on the virtual table such as (the count of students for courses tutorials taken) - spring-boot

I need to retrieve each student with number of courses taken, for example :
count
student
5
John
8
Jenny
And I need the following JSON response from my rest end-point:
[
{
"name_student": John,
"count_course": 5,
},
{
"name_student": Jenny,
"count_course": 8,
},
]
I have made the query to fetch the required data inside the Repository:
#Query(value ="Select count(*), s.student_name from student s INNER JOIN courses c on c.student_id= s.student_id group by s.student_name", nativeQuery = true )
List<StudentDTO> getIncidentCountByOfficerKPI ()
My DTO:
#Data
#NoArgsConstructor
public class StudentDTO {
Integer count;
String StudentName;
}
Service:
public List<StudentDTO> getStudentCount() {
return p2StudentRepository.getIncidentCountByOfficerKPI ();
}
Controller:
#GetMapping("/Student-count")
public ResponseEntity<StudentDTO> getAllStudentCount() {
List<StudentDTO> selectedCount = p2StudentService.getStudentCount();
List<StudentDTO> result = new ArrayList<>();
selectedCount.forEach(i -> {
StudentDTOitem = new StudentDTO();
item.setUserName(i.getStudentName());
item.setCount(i.getCount());
result.add(item);
});
return ResponseEntity.ok(result);
}
when I test with postman, it returns to nothing like below
[]
How can I fix that?

The return type of your rest end-point is incorrect, it should be ResponseEntity<List<StudentDTO>> (since you need to place multiple StudentDTO in the response).
And to provide ResponseEntity with a response body, you can use body() method of the ResponseEntity.BodyBuilder (that's a builder-object produced by the static method ResponseEntity.ok()), or it can be done by using of the parameterized constructors of the ResponseEntity.
And since you're actually not transforming the data returned by getStudentCount() you can attach this list as the response body directly.
#GetMapping("/Student-count")
public ResponseEntity<List<StudentDTO>> getAllStudentCount() { // <- change the return type
List<StudentDTO> selectedCount = p2StudentService.getStudentCount();
return ResponseEntity.ok()
.body(selectedCount); // <- add the response body
}

Related

InvalidPathException while sorting with org.springframework.data.domain.Pageable

I am trying to sort my table's content on the backend side, so I am sending org.springframework.data.domain.Pageable object to controller. It arrives correctly, but at the repository I am getting org.hibernate.hql.internal.ast.InvalidPathException. Somehow the field name I would use for sorting gets an org. package name infront of the filed name.
The Pageable object logged in the controller:
Page request [number: 0, size 10, sort: referenzNumber: DESC]
Exception in repository:
Invalid path: 'org.referenzNumber'","logger_name":"org.hibernate.hql.internal.ast.ErrorTracker","thread_name":"http-nio-8080-exec-2","level":"ERROR","level_value":40000,"stack_trace":"org.hibernate.hql.internal.ast.InvalidPathException: Invalid path: 'org.referenzNumber'\n\tat org.hibernate.hql.internal.ast.util.LiteralProcessor.lookupConstant(LiteralProcessor.java:111)
My controller endpoint:
#GetMapping(value = "/get-orders", params = { "page", "size" }, produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<PagedModel<KryptoOrder>> getOrders(
#ApiParam(name = "searchrequest", required = true) #Validated final OrderSearchRequest orderSearchRequest,
#PageableDefault(size = 500) final Pageable pageable, final BindingResult bindingResult,
final PagedResourcesAssembler<OrderVo> pagedResourcesAssembler) {
if (bindingResult.hasErrors()) {
return ResponseEntity.badRequest().build();
}
PagedModel<Order> orderPage = PagedModel.empty();
try {
var orderVoPage = orderPort.processOrderSearch(resourceMapper.toOrderSearchRequestVo(orderSearchRequest), pageable);
orderPage = pagedResourcesAssembler.toModel(orderVoPage, orderAssembler);
} catch (MissingRequiredField m) {
log.warn(RESPONSE_MISSING_REQUIRED_FIELD, m);
return ResponseEntity.badRequest().build();
}
return ResponseEntity.ok(orderPage);
}
the repository:
#Repository
public interface OrderRepository extends JpaRepository<Order, UUID> {
static final String SEARCH_ORDER = "SELECT o" //
+ " FROM Order o " //
+ " WHERE (cast(:partnerernumber as org.hibernate.type.IntegerType) is null or o.tradeBasis.account.retailpartner.partnerbank.partnerernumber = :partnerernumber)"
+ " and (cast(:accountnumber as org.hibernate.type.BigDecimalType) is null or o.tradeBasis.account.accountnumber = :accountnumber)"
+ " and (cast(:orderReference as org.hibernate.type.LongType) is null or o.tradeBasis.referenceNumber = :orderReference)"
+ " and (cast(:orderReferenceExtern as org.hibernate.type.StringType) is null or o.tradeBasis.kundenreferenceExternesFrontend = :orderReferenceExtern)"
+ " and (cast(:dateFrom as org.hibernate.type.DateType) is null or o.tradeBasis.timestamp > :dateFrom) "
+ " and (cast(:dateTo as org.hibernate.type.DateType) is null or o.tradeBasis.timestamp < :dateTo) ";
#Query(SEARCH_ORDER)
Page<Order> searchOrder(#Param("partnerernumber") Integer partnerernumber,
#Param("accountnumber") BigDecimal accountnumber, #Param("orderReference") Long orderReference,
#Param("orderReferenceExtern") String orderReferenceExtern, #Param("dateFrom") LocalDateTime dateFrom,
#Param("dateTo") LocalDateTime dateTo, Pageable pageable);
}
Update:
I removed the parameters from the sql query, and put them back one by one to see where it goes sideways. It seems as soon as the dates are involved the wierd "org." appears too.
Update 2:
If I change cast(:dateTo as org.hibernate.type.DateType) to cast(:dateFrom as date) then it appends the filed name with date. instead of org..
Thanks in advance for the help
My guess is, Spring Data is confused by the query you are using and can't properly append the order by clause to it. I would recommend you to use a Specification instead for your various filters. That will not only improve the performance of your queries because the database can better optimize queries, but will also make use of the JPA Criteria API behind the scenes, which requires no work from Spring Data to apply an order by specification.
Since your entity Order is named as the order by clause of HQL/SQL, my guess is that Spring Data tries to do something stupid with the string to determine the alias of the root entity.

Spring Webflux - R2dbc : How to run a child query and update value while iterating a result set

I am new to Reactive repositories and webflux. I am fetching a list of data from DB, iterating it using map() to build a DTO class object, in this process I need to run another query to get the count value and update the same DTO object. When I try as follows, the count is set to null
#Repository
public class CandidateGroupCustomRepo {
public Flux<CandidateGroupListDTO> getList(BigInteger userId){
final String sql = "SELECT gp.CANDIDATE_GROUP_ID,gp.NAME ,gp.GROUP_TYPE \n" +
" ,gp.CREATED_DATE ,cd.DESCRIPTION STATUS ,COUNT(con.CANDIDATE_GROUP_ID)\n" +
" FROM ........" +
" WHERE gp.CREATED_BY_USER_ID = :userId GROUP BY gp.CANDIDATE_GROUP_ID,gp.NAME ,gp.GROUP_TYPE \n" +
" ,gp.CREATED_DATE ,cd.DESCRIPTION";
return dbClient.execute(sql)
.bind("userId", userId)
.map(row ->{
CandidateGroupListDTO info = new CandidateGroupListDTO();
info.setGroupId(row.get(0, BigInteger.class));
info.setGroupName(row.get(1, String.class)) ;
info.setGroupType(row.get(2, String.class));
info.setCreatedDate( row.get(3, LocalDateTime.class));
info.setStatus(row.get(4, String.class));
if(info.getGroupType().equalsIgnoreCase("static")){
info.setContactsCount(row.get(5, BigInteger.class));
}else{
getGroupContactCount(info.getGroupId()).subscribe(count ->{
System.out.println(">>>>>"+count);
info.setContactsCount(count);
});
}
return info;
}
)
.all() ;
}
Mono<BigInteger> getGroupContactCount(BigInteger groupId){
final String sql = "SELECT 3 WHERE :groupId IS NOT NULL;";
return dbClient.execute(sql)
.bind("groupId", groupId)
.map(row -> {
System.out.println(row.get(0, BigInteger.class));
return row.get(0, BigInteger.class);
} ).one();
}
}
When I call getGroupContactCount, I am trying to extract count from Mono<BigInteger> and set it in my DTO.... sys out prints the count value correctly but still I get null for count in response.
You are calling subscribe in the middle which in turn is essentially blocking. The one subscribing is usually the final consumer, which im guessing your spring application is not, most likely the final consumer is the webpage that initiated the call. Your server is the producer.
call the database, flatMap and return.
return dbClient.execute(sql)
.bind("userId", userId)
.flatMap(row ->{
CandidateGroupListDTO info = new CandidateGroupListDTO();
info.setGroupId(row.get(0, BigInteger.class));
info.setGroupName(row.get(1, String.class)) ;
info.setGroupType(row.get(2, String.class));
info.setCreatedDate( row.get(3, LocalDateTime.class));
info.setStatus(row.get(4, String.class));
if(info.getGroupType().equalsIgnoreCase("static")){
return Mono.just(info.setContactsCount(row.get(5, BigInteger.class)));
} else {
return getGroupContactCount(info.getGroupId()).flatMap(count -> {
info.setContactsCount(count);
return Mono.just(info)
});
}
}).all();
Use map if order matters, otherwise try to use flatMap to do async work.

if/else doese not work in my POST Method of SpringBoot rest api

I use Spring Boot rest api with MongoDB.
In the POST Method, if there is not scoreID and there is not a player with specific date in my collection, because at the same time a player cannot play different games and bring score, then if the specific player and gamecode exist, create a score.
In fact, in the POST Method I used Nested IF-ELSE conditions.
But, in the Postman when I execute POST Request with this data:
{
"scoreid":"s11",
"score":1000,
"player":"sahari",
"gamecode":"g12",
"date":"2020-01-01"
}
always, I recieve an error, in the Postman, 400 Bad Request!, which i defined in the last line of my IF-ELSE statements.
I do not know, what is my mistake and why my program doese not execute IF conditions correct.
The POST Method:
//Create Score
#PostMapping
public ResponseEntity<?> createScore(#RequestBody #JsonView(Views.class) #Valid Score score) {
String p = srepo.findByPlayerName(score.getPlayer());
String g = srepo.findByGameCode(score.getGamecode());
String scoreid = srepo.findByScoreid(score.getScoreid());
Query query = new Query();
query.addCriteria(new Criteria().andOperator(Criteria.where("player").is(score.getPlayer()),
Criteria.where("date").is(score.getDate())));
if((scoreid != null)) {
return ResponseEntity.status(409).body("Conflict!"); }
else
if(mongoTemplate.exists(query, Score.class))
return ResponseEntity.status(409).body("There is not Possible at same time one player brings different Scores!");
else
if((p!= null)&&(g!= null))
{
history = new ArrayList<History>();
h = new History();
h.setScore(score.getScore());
h.setDate(score.getDate());
history.add(h);
hrepo.save(h);
score.setHistory(history);
srepo.insert(score);
return ResponseEntity.ok(score);
}
else
{
return ResponseEntity.status(400).body("Bad Request!");
}
}
The Score Repository:
#Repository
public interface ScoreRepository extends MongoRepository<Score, String>{
#Query("{'scoreid':?0}")
public String findByScoreid(String scoreid);
#Query("{'Player.nickname':?0}")
public String findByPlayerName(String player);
#Query("{'Games.code':?0}")
public String findByGameCode(String game);
}
The problem is not for my IF-ELSE statements.The problem is in the Score Repository
I must return a List instead of String for findByPlayerName and findByGameCode and for findByScoreid which is for checking duplicate in the POST Method I must return type of Score

JPA Criteria api - Total records for concrete query within pagination

I am programming function for pagination in my repository layer. Function receive as parameters spring's pageable object and some value like this:
public Page<Foo> filterFoo(Pageable pageable, String value) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Foo> fooQuery = cb.createQuery(Foo.class);
Root<Foo> foo = fooQuery .from(Foo.class);
fooQuery .where(adding predicate for match value);
List<Foo> result = entityManager.createQuery(fooQuery )
.setFirstResult((pageable.getPageNumber() - 1) * pageable.getPageSize())
.setMaxResults(pageable.getPageSize())
.getResultList();
return new PageImpl<>(result, pageable, xxxx);
}
Function return spring's PageImpl object filled with my result. To PageImpl I also need set total count of objects which suit predicates. This count number have to be of course without maxResult and firstResult. Is possible create another database call with my fooQuery to get total database records for that query without limit? What is the best practise to use pageable and criteria api in JPA? Thank you in advice.
Because generated SQL uses aliases - you may need make separate query for get total count of rows.
For example:
CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
countQuery.select(cb.count(countQuery.from(Foo.class)));
if (Objects.nonNull(filters)) {
countQuery.where(filters);
}
return new PageImpl<>(result, pageable, em.createQuery(countQuery).getSingleResult());
where filters is equal to your adding predicate for match value expression.
Also, you may use a TupleQuery with custom SQL function for calculate count of rows in one select query.
Like this:
public class SqlFunctionsMetadataBuilderContributor implements MetadataBuilderContributor {
#Override
public void contribute(MetadataBuilder metadataBuilder) {
metadataBuilder.applySqlFunction(
"count_over",
new SQLFunctionTemplate(
StandardBasicTypes.LONG,
"(count(?1) over())"
)
);
}
}
and Criteria:
public Page<Foo> findAll(Specification<Foo> specification, Pageable pageable) {
CriteriaQuery<Tuple> cq = cb.createTupleQuery();
Root<Foo.class> fooRoot = cq.from(Foo.class);
cq.select(cb.tuple(fooRoot, cb.function("count_over", Long.class, fooRoot.get("id"))));
Predicate filters = specification.toPredicate(fooRoot, cq, cb);
if (Objects.nonNull(filters)) {
cq.where(filters);
}
TypedQuery<Tuple> query = em.createQuery(cq);
query.setFirstResult((int) pageable.getOffset());
query.setMaxResults(pageable.getPageSize());
List<Tuple> result = query.getResultList();
if (result.isEmpty()) {
return new PageImpl<>(List.of());
}
return new PageImpl<>(
result.stream().map(tuple -> (Foo) tuple.get(0)).collect(toUnmodifiableList()),
pageable,
(long) result.get(0).get(1)
);
}
See more about SQLFunction: https://vladmihalcea.com/hibernate-sql-function-jpql-criteria-api-query/ and Custom SQL for Order in JPA Criteria API

How can I have two return types in one controller returning Collection of Class A or Object of Class B

I am trying to follow Rest Principles, so I have two rest controllers
with a base URL localhost:8088/trucks.
First, I have to get all trucks:
#GetMapping(value = "/trucks")
public final Collection<TruckDto> getAllTrucks() {
LOGGER.debug("test: getAllTrucks()");
Collection<Truck> trucks = truckService.getAllTrucks();
return mappingService.map(trucks, TruckDto.class);
}
Note, my issue also has to do with the fact that I have different classes I am returning for truckById and truckByTruckCode.
And I have 'get truck by ID' service:
#GetMapping(value = "/trucks/{truckId:[0-9]+}")
#ResponseStatus(HttpStatus.FOUND)
#ResponseBody
public final TruckDto getTruckId(#PathVariable(value = "truckId") final Integer truckId) {
LOGGER.debug("test: truckId({})", truckId);
Truck truck = truckService.getTruckById(truckId);
return mappingService.map(truck, TruckDto.class);
}
Now I have a get by truckCode that works but it doesnt follow the rest principle , which is something like , there should be only one base url and all others build from it , here it is
#ResponseStatus(HttpStatus.OK)
#ResponseBody
#GetMapping(value = "/trucks/{truckCode:[0-9]*[a-zA-Z][a-zA-Z0-9]*}")
public final TruckWithAvgPetrolDto getTruckByTruckCode (#PathVariable(value = "truckCode")
final String truckCode) {
LOGGER.debug("getTruckByTruckCode()");
TruckWithAvgDto truck = truckService.getTruckByTruckCode(truckCode);
return mappingService.map(truck, TruckWithAvgPetrolDto.class);
}
Now it works but I think it should be an optional param there for it should be in one method. So maybe I can have some sort of optional return type because I have two different services, methods to return in both situations.
Because get by id just gets truck detail, but get by trukCode performs a left join and gets truck average consumption of petrol from an order table so I have two different DTOs for get by id and get by truckCode.
My questions are how can I get something like one method say getBY(param) if I put a number it should get by id and return TruckDto but if I put a code like BY788 it should get by code and return a TruckWithAvgPetrolDto. Is that's possible?
This is a solution that works but i will need verification that it is ok to do such in rest or it is bad practice
#GetMapping("/trucks/{value}")
public ResponseEntity<?> getTruckByIdOrCode(#PathVariable(value = "value" )String value) {
if (value.matches("[0-9]*[a-zA-Z][a-zA-Z0-9]*")) {
TruckWithAvgDto list = truckService.getTruckByTruckCode(value);
return new ResponseEntity<TruckWithAvgDto>(list, HttpStatus.FOUND);
}else {
Truck truck = truckService.getTruckById(Integer.parseInt(value));
return new ResponseEntity<Truck>(truck,HttpStatus.FOUND);
}
}

Resources