Test spring repository with pageRequest - java

Im have repository:
#Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
List<Product> findAllByPriceBetween(BigDecimal from, BigDecimal to, PageRequest pageRequest);
}
In liquibase im just create table:
--liquibase formatted sql
--changeset <sniklz>:<create-products-table>
CREATE TABLE IF NOT EXISTS products
(
id bigint auto_increment,
title varchar(255) not null,
price decimal not null,
CONSTRAINT product_pl PRIMARY KEY (id)
);
--rollback DROP TABLE products;
then im try to test my repository:
#DataJpaTest
#Testcontainers
#AutoConfigureTestDatabase (replace = AutoConfigureTestDatabase.Replace.NONE)
class ProductRepositoryTest {
#Container
static MySQLContainer<?> database = new MySQLContainer<>("mysql:8")
.withDatabaseName("springboot")
.withPassword("springboot")
.withUsername("springboot");
#DynamicPropertySource
static void setDataSourceProperties(DynamicPropertyRegistry propertyRegistry) {
propertyRegistry.add("spring.datasource.url", database::getJdbcUrl);
propertyRegistry.add("spring.datasource.username", database::getUsername);
propertyRegistry.add("spring.datasource.password", database::getPassword);
}
#Autowired
private ProductRepository productRepository;
#Test
#Sql("/scripts/init_four_products.sql")
void shouldReturnProductPriceGreaterThan1200() {
List<Sort.Order> order = new ArrayList<>();
order.add(new Sort.Order(Sort.Direction.DESC, "price"));
Sort sort = Sort.by(order);
PageRequest pageRequest = PageRequest.of(0, 10, sort);
List<Product> acutal = productRepository.findAllByPriceBetween(BigDecimal.valueOf(1200),
BigDecimal.valueOf(Integer.MAX_VALUE), pageRequest);
Assertions.assertEquals(1, acutal.size());
Assertions.assertEquals("iPhone XI", acutal.get(0).getTitle());
}
}
in /scripts/init_four_products.sql im just create som products:
INSERT INTO products (title, price) VALUES ("iPhone 8", 700);
INSERT INTO products (title, price) VALUES ("iPhone 9 ", 800);
INSERT INTO products (title, price) VALUES ("iPhone X", 1000);
INSERT INTO products (title, price) VALUES ("iPhone XI", 1200);
But have error:
org.springframework.dao.InvalidDataAccessApiUsageException: At least 3
parameter(s) provided but only 2 parameter(s) present in query.;
nested exception is java.lang.IllegalArgumentException: At least 3
parameter(s) provided but only 2 parameter(s) present in query.
did not find any useful information at google

According to Spring documentation:
The infrastructure recognizes certain specific types like Pageable and
Sort, to apply pagination and sorting to your queries dynamically.
With that being said, you should replace PageRequest with Pageable in the ProductRepository method:
#Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
List<Product> findAllByPriceBetween(BigDecimal from, BigDecimal to, Pageable pageable);
}
Pageable should be instantiated as follows:
Pageable pageable = PageRequest.of(0, 10, sort);

Related

Example of spring using Pageable, Example and Sort accessing a JPA repository

I searched everywhere for an example of a Spring piece of code using simultaneously these 3 JPA concepts, very important when querying :
filtering - using Example, ExampleMatcher
paging - using Pageable (or similar)
sorting - using Sort
So far I only saw examples using only 2 of them at the same time but I need to use all of them at once. Can you show me such an example?
Thank you.
PS: This has examples for Paging and Sorting but not filtering.
Here is an example, searching for news on title attribute, with pagination and sorting :
Entity :
#Getter
#Setter
#Entity
public class News {
#Id
private Long id;
#Column
private String title;
#Column
private String content;
}
Repository :
public interface NewsRepository extends JpaRepository<News, Long> {
}
Service
#Service
public class NewsService {
#Autowired
private NewsRepository newsRepository;
public Iterable<News> getNewsFilteredPaginated(String text, int pageNumber, int pageSize, String sortBy, String sortDirection) {
final News news = new News();
news.setTitle(text);
final ExampleMatcher matcher = ExampleMatcher.matching()
.withIgnoreCase()
.withIgnorePaths("content")
.withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING);
return newsRepository.findAll(Example.of(news, matcher), PageRequest.of(pageNumber, pageSize, sortDirection.equalsIgnoreCase("asc") ? Sort.by(sortBy).ascending() : Sort.by(sortBy).descending()));
}
}
Call example :
for (News news : newsService.getNewsFilteredPaginated("hello", 0, 10, "title", "asc")) {
log.info(news.getTitle());
}
Found the answer in the end after more research:
public Page<MyEntity> findAll(MyEntity entityFilter, int pageSize, int currentPage){
ExampleMatcher matcher = ExampleMatcher.matchingAll()
.withMatcher("name", exact()); //add filters for other columns here
Example<MyEntity> filter = Example.of(entityFilter, matcher);
Sort sort = Sort.by(Sort.Direction.ASC, "id"); //add other sort columns here
Pageable pageable = PageRequest.of(currentPage, pageSize, sort);
return repository.findAll(filter, pageable);
}

Variables in Spring Data JPA native query

Using Spring Dat JPA, I need to query my database and return a range of OrderEntitys based on a startAmt and a endAmt of amounts. I'm not sure if I should map these two variables to entity OrderEntity, as fields in some type of separate class/entity/model, or simply declare them in my native query. Perhaps I should be using a service that implements EntityManager.createNativeQuery()?
Would like to do something like :
#Repository
public interface OrderRangeRepository extends JpaRepository<OrderEntity, OrderEntityID> {
#Query(value = "SELECT * FROM Orders WHERE Amount BETWEEN startAmt AND endAmt;" , nativeQuery=true)
List<OrderEntity> findOrdersBy(int startAmt, int endAmt);
}
If I were to use EntityManager.createNativeQuery() in a service, perhaps something like below :
#Service
public class OrderRangeService {
#Autowired
EntityManager entityManager;
public List<OrderEntity> findAmountsBetween() {
List<OrderEntity> amountsBetween = entityManager.createNativeQuery("SELECT * FROM Orders WHERE Amount BETWEEN ?1 AND 2?;")
.setParameter(1, "startAmt")
.setParameter(2, "endAmt")
.getResultList();
return amountsBetween;
}
}
You can achieve this with Spring Data JPA without defining a native query.
#Repository
public interface OrderRangeRepository extends JpaRepository<OrderEntity, OrderEntityID> {
List<OrderEntity> findByAmountBetween(int startAmt, int endAmt);
}
If you want to use the native query change it to
#Query(value = "SELECT * FROM Orders WHERE Amount BETWEEN :startAmt AND :endAmt" , nativeQuery=true)
List<OrderEntity> findOrdersBy(#Param("startAmt") int startAmt, #Param("endAmt") int endAmt);
You can invoke the query in a service by doing
#Service
public class OrderRangeService {
#Autowired
OrderRangeRepository orderRangeRepository ;
public List<OrderEntity> findAmountsBetween(int startAmt, int endAmt) {
List<OrderEntity> amountsBetween = orderRangeRepository.findByAmountBetween(startAmt, endAmt);
return amountsBetween;
}
}
Finally, from your controller, you should autowire the OrderRangeService and invoke the findAmountsBetween service method
#Autowired
OrderRangeService orderRangeService;
#GetMapping("/amountsFromAndTo")
#ResponseBody
public String getAmounts(#RequestParam int startAmt, #RequestParam int endAmt) {
List<OrderEntity> orderEntityL = orderRangeService.findAmountsBetween(startAmt, endAmt);
return orderEntityL.toString();
}
1. Named Parameters
Each parameter annotated with #Param must have a value string matching
the corresponding JPQL or SQL query parameter name. A query with named
parameters is easier to read and is less error-prone in case the query
needs to be refactored.
#Query(value = "SELECT * FROM Orders WHERE Amount BETWEEN :startAmt AND :endAmt;" , nativeQuery=true)
List<OrderEntity> findOrdersBy(#Param("startAmt") int startAmt, #Param("endAmt") int endAmt);
}
2. Indexed Query Parameters
Spring Data will pass method parameters to the query in the same order
they appear in the method declaration
#Query(value = "SELECT * FROM Orders WHERE Amount BETWEEN ?1 AND ?2;" , nativeQuery=true)
List<OrderEntity> findOrdersBy(int startAmt, int endAmt);

Returned object from Spring Data Jpa query has null values

I'm trying to get object of custom type from JPA Repository
VisitRepository.java
#Repository
public interface VisitRepository extends JpaRepository<Visit, Long>, JpaSpecificationExecutor<Visit> {
#Query(value = "select client_id , count(*) from visit where (DATE(jhi_date) between :startDate and :endDate) group by client_id",nativeQuery = true)
List<IIntegerReportData> findByDate(#Param("startDate") String startDate, #Param("endDate") String endDate);
IIntegerReportData.java
package com.mycompany.hiptest.repository;
public interface IIntegerReportData {
Long getId();
Integer getValue();
}
ClientRating.java
public List<ClientsRatingDTO> findAllSorted(String startDate, String endDate, Long fieldNum) {
List<IIntegerReportData> visitReport = visitRepository.findByDate(startDate, endDate);
log.debug("visitReport:" + visitReport.size());
for (IIntegerReportData visit : visitReport
) {
log.debug("value: " + visit.getValue());
}
In debug i get visitReport.size() = 27 (that is correct records count), but
visit.getValue() is NULL for each rows, although there are not null values in this field for each rows.
What's wrong?
You could use NativeQuery Annotation:
Have a look at:
https://www.baeldung.com/spring-data-jpa-query
When returning a custom object from a native query, the result column names must match the names of the custom interface, otherwise they'll just have null values. E.g.:
#Repository
public interface MyRepository extends JpaRepository<MyEntity, Long> {
#Query(value = "SELECT "\"id\" FROM \"my_entity\"", nativeQuery = true)
List<IdNative> findAllIdNative();
interface IdNative {
Long getEntityId();
}
}
Here, getEntityId() will always return null because the result table of the query has no entityId column. To fix, either change the query to match the method:
SELECT "id" AS "entityId" FROM "my_entity"
Or, change the interface method to match the column name:
Long getId();

Spring CrudRepository query with multiple In conditions

I'm trying to get data from a dynamodb table using Spring Data, filtering by two fields using In keyword, but it always return an empty result.
I have an entity like:
#DynamoDBTable(tableName = "my-table")
public class MyEntity {
#Id
private MyTableKey id;
private String saleDate;
private Long sellerId;
// Some other properties
#DynamoDBHashKey
public String getSaleDate() {
return this.saleDate;
}
#DynamoDBRangeKey
public Long getSellerId() {
return this.sellerId;
}
}
and my interface for the repository is
#EnableScan
#EnableScanCount
public interface MySalesRepository
extends PagingAndSortingRepository<MyEntity, MyTableKey> {
Page<MyEntity> findById (MyTableKey id, Pageable pageable);
Page<MyEntity> findAllBySaleDateAndSellerIdIn (List<String> saleDate,
List<Long> sellerId, Pageable pageable);
}
I want to get items that have both saleDate in the saleDate list and sellerId in the sellerId list, but I get 0 results.
How can I get the values I want using And and In keywords?
Try changing this:
Page<MyEntity> findAllBySaleDateAndSellerIdIn (List<String> saleDate,
List<Long> sellerId, Pageable pageable);
to this:
Page<MyEntity> findBySaleDateInAndSellerIdIn (List<String> saleDate,
List<Long> sellerId, Pageable pageable);

Spring boot: Optional parameter query in Query method

I am new to Spring boot and hibernate. Here I am trying run a search based optional parameter query Where i can search by name, country etc. If I kept this field null then query should all list. But the problem is my method is returning all data ignoring my search parameter. my model class look like
#Entity(name="MLFM_ORDER_OWNER")
public class ModelOrderOwner {
#Id #GenericGenerator(name = "custom_sequence", strategy =
"com.biziitech.mlfm.IdGenerator")
#GeneratedValue(generator = "custom_sequence")
#Column(name="ORDER_OWNER_ID")
private Long orderOwnerId;
#Column(name="OWNER_NAME")
private String ownerName;
#OneToOne
#JoinColumn(name="BUSINESS_TYPE_ID")
private ModelBusinessType businessTypeId;
#Column(name="SHORT_CODE")
private String shortCode;
#ManyToOne
#JoinColumn(name="OWNER_COUNTRY")
private ModelCountry ownerCountry;
// getter setter..
My Repository interface looks like
public interface OrderOwnerRepository extends
JpaRepository<ModelOrderOwner,Long>{
#Query("select a from MLFM_ORDER_OWNER a where a.businessTypeId.typeId=coalsec(:typeId,a.businessTypeId.typeId) and a.ownerCountry.countryId=coalsec(:countryId,a.ownerCountry.countryId) and a.ownerName LIKE %:name and a.shortCode LIKE %:code")
public List <ModelOrderOwner> findOwnerDetails(#Param("typeId")Long typeId,#Param("countryId")Long countryId,#Param("name")String name,#Param("code")String code);
}
And here is my method in controller
#RequestMapping(path="/owners/search")
public String getAllOwner(Model model,#RequestParam("owner_name") String name,#RequestParam("shortCode") String code,
#RequestParam("phoneNumber") String phoneNumber,#RequestParam("countryName") Long countryId,
#RequestParam("businessType") Long typeId
) {
model.addAttribute("ownerList",ownerRepository.findOwnerDetails(typeId, countryId, name, code));
return "data_list";
}
Can Any one help me in this regard? please?
It is too late too answer, but for anyone who looks for a solution yet there is a more simple way as below:
In my case my controller was like:
#RestController
#RequestMapping("/order")
public class OrderController {
private final IOrderService service;
public OrderController(IOrderService service) {
this.service = service;
}
#RequestMapping(value = "/{username}/", method = RequestMethod.GET)
public ResponseEntity<ListResponse<UserOrdersResponse>> getUserOrders(
#RequestHeader Map<String, String> requestHeaders,
#RequestParam(required=false) Long id,
#RequestParam(required=false) Long flags,
#RequestParam(required=true) Long offset,
#RequestParam(required=true) Long length) {
// Return successful response
return new ResponseEntity<>(service.getUserOrders(requestDTO), HttpStatus.OK);
}
}
As you can see, I have Username as #PathVariable and length and offset which are my required parameters, but I accept id and flags for filtering search result, so they are my optional parameters and are not necessary for calling the REST service.
Now in my repository layer I have just created my #Query as below:
#Query("select new com.ada.bourse.wealth.services.models.response.UserOrdersResponse(FIELDS ARE DELETED TO BECOME MORE READABLE)" +
" from User u join Orders o on u.id = o.user.id where u.userName = :username" +
" and (:orderId is null or o.id = :orderId) and (:flag is null or o.flags = :flag)")
Page<UserOrdersResponse> findUsersOrders(String username, Long orderId, Long flag, Pageable page);
And that's it, you can see that I checked my optional arguments with (:orderId is null or o.id = :orderId) and (:flag is null or o.flags = :flag) and I think it needs to be emphasized that I checked my argument with is null condition not my columns data, so if client send Id and flags parameters for me I will filter the Result with them otherwise I just query with username which was my #PathVariable.
Don't know how but below code is working for me:
#Query("select a from MLFM_ORDER_OWNER a
where a.businessTypeId.typeId=COALESCE(:typeId,a.businessTypeId.typeId)
and a.ownerCountry.countryId=COALESCE(:countryId,a.ownerCountry.countryId)
and a.ownerName LIKE %:name and a.shortCode LIKE %:code")
public List <ModelOrderOwner> findOwnerDetails(
#Param("typeId")Long typeId,
#Param("countryId")Long countryId,
#Param("name")String name,
#Param("code")String code);
and in my controller class:
#RequestMapping(path="/owners/search")
public String getAllOwner(Model model,
#RequestParam("owner_name") String name,
#RequestParam("shortCode") String code,
#RequestParam("phoneNumber") String phoneNumber,
#RequestParam("countryName") Long countryId,
#RequestParam(value = "active", required = false) String active, #RequestParam("businessType") Long typeId) {
if(typeId==0)
typeId=null;
if(countryId==0)
countryId=null; model.addAttribute("ownerList",ownerRepository.findOwnerDetails(typeId, countryId, name, code, status));
return "data_list";
}
JPQL doesn't support optional parameters.
There is no easy way of doing this in JPQL. You will have to write multiple WHERE clauses with OR operator.
Refer these answers to similar questions: Answer 1 & Answer 2
PS: You might want to look into Query by Example for your use case. It supports handling of null parameters.
Use JpaSpecificationExecutor //import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
Step 1: Implement JpaSpecificationExecutor in your JPA Repository
Ex:
public interface TicketRepo extends JpaRepository<Ticket, Long>, JpaSpecificationExecutor<Ticket> {
Step 2 Now to fetch tickets based on optional parameters you can build Specification query using CriteriaBuilder
Ex:
public Specification<Ticket> getTicketQuery(Integer domainId, Calendar startDate, Calendar endDate, Integer gameId, Integer drawId) {
return (root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
predicates.add(criteriaBuilder.equal(root.get("domainId"), domainId));
predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("createdAt"), startDate));
predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("createdAt"), endDate));
if (gameId != null) {
predicates.add(criteriaBuilder.equal(root.get("gameId"), gameId));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
}
Step 3: Pass the Specification instance to jpaRepo.findAll(specification), it will return you the list of your entity object (Tickets here in the running example)
ticketRepo.findAll(specification); // Pass output of function in step 2 to findAll

Categories