I've encountered a problem:
I'm accepting the sortBy string in the controller and creating Sort object with Sort.by(sortBy).
The problem is that I can't seem to find a way to block fields from being sorted.
For example, I have:
#Column(nullable = false)
private String encryptedPassword;
I would like to block being able to sort by a password.
My controller method:
#GetMapping(produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_XML_VALUE }, consumes = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public PagedModel<EntityModel<UserDetailsResponse>> getUsers(#RequestParam(required = false, defaultValue = "0") Integer page,
#RequestParam Integer size,
#RequestParam(required = false, defaultValue = "createDate") String sortBy,
#RequestParam(required = false) String order) {
Sort.Direction sortDirection = Sort.Direction.fromOptionalString(order)
.orElse(Sort.Direction.DESC);
PageRequest pageRequest = PageRequest.of(page, size, Sort.by(sortDirection, sortBy));
Page<UserDTO> users = userService.getUsers(pageRequest);
Page<UserDetailsResponse> userDetailsResponses = users.stream()
.map((userDTO) -> modelMapper.map(userDTO, UserDetailsResponse.class))
.map(this::addRelations)
.collect(Collectors.collectingAndThen(Collectors.toList(), PageImpl::new));
return pagedResourcesAssembler.toModel(userDetailsResponses);
}
Of course, I could create a blacklist with field names, but I'm looking for a non-hardcoding way.
Is there some annotation or other way I could use to achieve that?
Since there doesn't appear to be any annotation for that, I wrote my own.
If someone stumbles upon this problem here's the code:
Annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface SortBlacklisted {
}
SortBlackListUtil:
#Component
public class SortBlackListUtil {
/**
* Looks for fields annotated with #SortBlacklisted in the specified class
* and creates blacklist form their names.
*
* #param classToLookIn Class to search in for #SortBlacklisted annotation.
* #param <T> Class object type.
* #return List of blacklisted fields names for the specified class.
*/
public <T> ArrayList<String> getBlackListedFields(Class<T> classToLookIn) {
ArrayList<String> blackListedFieldsNames = new ArrayList<>();
for (Field field : classToLookIn.getDeclaredFields()) {
SortBlacklisted sortBlacklisted = field.getAnnotation(SortBlacklisted.class);
if (sortBlacklisted != null) blackListedFieldsNames.add(field.getName());
}
return blackListedFieldsNames;
}
}
Now simply place that an annotation on the desired field
#SortBlacklisted
private String encryptedPassword;
Use that method to get a list of blacklisted fields names:
sortBlackListUtil.getBlackListedFields(MyClass.class)
In our spring rest controller we would like to use the same mappings with different kind of parameters.
To do this we created additional functions differentiated by mapping params. By doing so we are duplicating the number of functions. To avoid this I would like to use different controllers that should be loaded based on params values.
The question is can we
#RequestMapping(value = "/v1")
#RestController
public class Controller {
#PostMapping(value = "/event-calendar", params = {"externalToken", "event_type"})
public ResponseEntity createEntityOfTypeToken(#RequestHeader(name = "X-Application-Authentication") String externalToken,
#RequestParam(value = "event_type") String eventType) {
MyEntity entity = service.createEntityOfType(
userService.getTokenService(externalToken).getDeviceSerialNumber());
return new ResponseEntity<>(entity, HttpStatus.OK);
}
#PostMapping(value = "/event-calendar", params = {"serialId", "event_type"})
public ResponseEntity createEntityOfTypeSerial(#RequestParam(value = "serialId") String serialId,
#RequestParam(value = "event_type") String eventType) {
MyEntity entity = service.createEntityOfType(serialId);
return new ResponseEntity<>(entity, HttpStatus.OK);
}
}
Please refer
create two method for same url pattern with different arguments
Spring - is possible to give same url in request mapping of post method?
P.S. not enough points to comment
I have a Spring Boot 1.5.2 project which up till now has been sending lists back to the view. I would like to use pagination instead of lists. I began to change the code in the service layer and repository, but it has not been a simple case of changing from List<T> to Page<T>.
In particular on the service layer I was returning different List<T> based on user role and then passing this list to a method that converts to Dto before sending it back to controller.
Spring Boot 1.5.2 appears to use Spring-data-jpa:1.11
Controller
#GetMapping("/dashboard/sale")
public String dashboard(#RequestParam(name = "p", defaultValue = "1") int pageNumber, Model model, HttpServletRequest request) {
List<SaleDashboard> listSaleDashboard = saleService.getPage(pageNumber);
model.addAttribute("listSaleDashboard", listSaleDashboard);
return "dashboard";
}
Service Layer
public List<SaleDashboard> getPage(int pageNumber) {
PageRequest request = new PageRequest(pageNumber - 1, PAGESIZE, Sort.Direction.ASC, "id");
List<Sale> listSale = new ArrayList<>();
if (roles.contains("ROLE_ADMIN")) {
listSale = saleRepository.findBySomeProperty(user.getUserDetails().getReportDept());
}
if (roles.contains("ROLE_USER")) {
listSale = saleRepository.findByListOfCreatingUser(userList);
}
List<SaleDashboard> listSaleDashboard = createSaleDashboard(listSale);
return listSaleDashboard;
}
public List<SaleDashboard> createSaleDashboard(List<Sale> sales) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
List<SaleDashboard> listSaleDashboard = new ArrayList<>();
for (Sale sale: sales) {
SaleDashboard saleDashboard = new SaleDashboard();
saleDashboard.setSaleId(sale.getId());
// ETC
listSaleDashboard.add(saleDashboard);
}
return listSaleDashboard;
}
In the service layer above I began to use PageRequest but thats as far as I got.
Repository
public interface SaleRepository extends JpaRepository<Sale, Long> , PagingAndSortingRepository<Sale, Long> {
#Query("SELECT e FROM Sale e WHERE e.creatingUser in (:userList)")
Page<Sale> findByListOfCreatingUser(#Param("userList") List<User> users, Pageable pageable);
}
How would I implement a similar service layer but use Page<T> instead of List<T>?
I have a class that returns a List<UserObject> containing 184 items to my repository. This isn't hitting a database but the List contains what I expect.
List<UserObject> objectList = UserObjectData.getObjectArray();
In my repository, I have a Pageable object that should return 2 items per page, starting at page 0. It has these attributes:
System.out.println(pageable.getPageSize()); // returns '2'
System.out.println(pageable.getOffset()); // returns '0'
System.out.println(pageable.getPageNumber()); // returns '0'
Then I create a Page<UserObject> like this:
Page<UserObject> theObjects = new PageImpl<UserObject>(objectList, pageable, objectList.size());
... and this is what comes back:
System.out.println(theObjects.getSize()); // returns '2'
System.out.println(theObjects.getNumber()); // returns '0'
System.out.println(theObjects.getNumberOfElements()); // returns '184'
System.out.println(theObjects.getTotalElements()); // returns '184'
System.out.println(theObjects.getTotalPages()); // returns '92'
I pass Page<UserObject> back to a controller that returns it to a REST client but that response contains all 184 items, not 2 like I expect.
Does the above code look correct; especially the new PageImpl<UserObject>? If so, my problems might be in how I'm handling the Page<UserObject>
in the controller and I'll focus on figuring out what I'm doing wrong there.
EDIT: following is the code in full:
#RestController
public class UserController {
private UserObjectService service;
NewUserRepository newUserRepository;
#RequestMapping(value = "getObjects", method = RequestMethod.GET)
public Page<UserObject> getObjects(#RequestParam int page, #RequestParam int size) {
Pageable pageable = new PageRequest(page,size);
newUserRepository = new NewUserRepository();
Page<UserObject> newPageResult = newUserRepository.findAll(pageable);
return newPageResult;
}
public class NewUserRepository implements PagingAndSortingRepository<UserObject, Long> {
public Page<UserObject> findAll(Pageable pageable) {
List<UserObject> objectList = UserObjectData.getObjectArray(); // I can verify this contains 184 items
Page<UserObject> theObjects = new PageImpl<UserObject>(objectList, pageable, objectList.size());
return theObjects;
}
Change page object creation to this:
Page<UserObject> theObjects = new PageImpl<UserObject>(objectList, pageable, (pageable.getOffset() + pageable.getPageSize()));
I am trying to convert list to page in spring. I have converted it using
new PageImpl(users, pageable, users.size());
But now I having problem with sorting and pagination itself. When I try passing size and page, the pagination doesn't work.
Here's the code I am using.
My Controller
public ResponseEntity<User> getUsersByProgramId(
#RequestParam(name = "programId", required = true) Integer programId Pageable pageable) {
List<User> users = userService.findAllByProgramId(programId);
Page<User> pages = new PageImpl<User>(users, pageable, users.size());
return new ResponseEntity<>(pages, HttpStatus.OK);
}
Here is my user Repo
public interface UserRepo extends JpaRepository<User, Integer>{
public List<User> findAllByProgramId(Integer programId);
Here is my service
public List<User> findAllByProgramId(Integer programId);
I had the same problem. I used subList:
final int start = (int)pageable.getOffset();
final int end = Math.min((start + pageable.getPageSize()), users.size());
final Page<User> page = new PageImpl<>(users.subList(start, end), pageable, users.size());
There is a Page implementation for that:
Page<Something> page = new PageImpl<>(yourList);
As indicated in the reference documentation, Spring Data repositories support pagination on query methods by simply declaring a parameter of type Pageable to make sure they're only reading the data necessary for the requested Page.
Page<User> page = findAllByProgramId(Integer programId, Pageable pageable);
That would return a Page object with the page size/settings defined in your Pageable object. No need to get a list and then try to create a page out of it.
You should do it like advised by the dubonzi's answer.
If you still want to use pagination for a given List use PagedListHolder:
List<String> list = // ...
// Creation
PagedListHolder page = new PagedListHolder(list);
page.setPageSize(10); // number of items per page
page.setPage(0); // set to first page
// Retrieval
page.getPageCount(); // number of pages
page.getPageList(); // a List which represents the current page
If you need sorting, use another PagedListHolder constructor with a MutableSortDefinition.
Try This:
public Page<Patient> searchPatientPage(SearchPatientDto patient, int page, int size){
List<Patient> patientsList = new ArrayList<Patient>();
Set<Patient> list=searchPatient(patient);
patientsList.addAll(list);
int start = new PageRequest(page, size).getOffset();
int end = (start + new PageRequest(page, size).getPageSize()) > patientsList.size() ? patientsList.size() : (start + new PageRequest(page, size).getPageSize());
return new PageImpl<Patient>(patientsList.subList(start, end), new PageRequest(page, size), patientsList.size());
}
This could be the solution. Sorting and pagination will work too this way:
Controller:
public ResponseEntity<User> getUsersByProgramId(
#RequestParam(name = "programId", required = true) Integer programId Pageable pageable) {
Page<User> usersPage = userService.findAllByProgramId(programId, pageable);
Page<User> pages = new PageImpl<User>(usersPage.getContent(), pageable, usersPage.getTotalElements());
return new ResponseEntity<>(pages, HttpStatus.OK);
}
Service:
Page<User> findAllByProgramId(Integer programId, Pageable pageable);
Repository:
public interface UserRepo extends JpaRepository<User, Integer>{
public Page<User> findAllByProgramId(Integer programId, Pageable pageable);
}
This way, we can also return different page of entity too.
In the JHipster framework there is an interface for such things PageUtil:
static <T> Page<T> createPageFromList(List<T> list, Pageable pageable) {
if (list == null) {
throw new IllegalArgumentException("To create a Page, the list mustn't be null!");
}
int startOfPage = pageable.getPageNumber() * pageable.getPageSize();
if (startOfPage > list.size()) {
return new PageImpl<>(new ArrayList<>(), pageable, 0);
}
int endOfPage = Math.min(startOfPage + pageable.getPageSize(), list.size());
return new PageImpl<>(list.subList(startOfPage, endOfPage), pageable, list.size());
}
You can use this generic function for converting List to page.
public static<T> Page<T> convertToPage(List<T> objectList, Pageable pageable){
int start = (int) pageable.getOffset();
int end = Math.min(start+pageable.getPageSize(),objectList.size());
List<T> subList = start>=end?new ArrayList<>():objectList.subList(start,end);
return new PageImpl<>(subList,pageable,objectList.size());
}
Implemented based on #shilaimuslm comment. In this case an exception will not be thrown if the start > end in subList.
List<User> users = // ...
Pageable paging = PageRequest.of(pagePagination, sizePagination);
int start = Math.min((int)paging.getOffset(), users.size());
int end = Math.min((start + paging.getPageSize()), users.size());
Page<User> page = new PageImpl<>(users.subList(start, end), paging, users.size());
//1) For a boot application create a paging repository interface
public interface PersonRepository extends PagingAndSortingRepository<Person,
String> {
// Common CURD method are automatically implemented
}
//2) create a service Interface
public interface PersonService {
Page<Person> listAllByPage(Pageable pageable); // Use common CURD findAll() method
Page<Object> listSpecByPage(Pageable pageable, String x);
}
//3) create a service Impl Class of service interface
#Service
public class PersonServiceImpl implements PersonService {
final PersonRepository personRepository;
#Autowired
PersonServiceImpl(PersonRepository personRepository){
this.personRepository = personRepository;
}
#Override
public Page<Person> listAllByPage(Pageable pageable) {
return personRepository.findAll(pageable);
}
#Override
public Page<Object> listSpecByPage(Pageable pageable, String path) {
List<Object> objectlist = new ArrayList<Object>();
// Do your process to get output in a list by using node.js run on a *js file defined in 'path' varriable
Page<Object> pages1 = new PageImpl<Object>(objectlist, pageable, objectlist.size());
return pages1;
}
}
//4) write your controller
public class PersonController {
final PersonService personService;
#Autowired
PersonController( PersonService personService ){
this.personService = personService;
}
#GetMapping("/get") // Use of findALL() function
Page<Person> listed( Pageable pageable){
Page<Person> persons = personService.listAllByPage(pageable);
return persons;
}
#GetMapping("/spec") // Use of defined function
Page<Object> listSpec( Pageable pageable, String path){
Page<Object> obj = personService.listSpecByPage(pageable, path);
return obj;
}
}
Thanks guys below code is working in my case
int start = pageble.getOffset();
int end = (start + pageble.getPageSize()) > vehicleModelsList.size() ? vehicleModelsList.size() : (start + pageble.getPageSize());
Have you tried extending your repository to PagingAndSortingRepository?
public interface UserRepo extends PagingAndSortingRepository<Ticket, Integer> {
Page<User> findAllByProgramId(Integer programId, Pageable pageable);
}
Service
Page<User> findAllByProgramId(Integer programId, Pageable pageable);
I assume you are using interface to the service:
Instead of returing complete array list take subblist as per your requirement.
You will get 'offset' and size from 'pageable' object in request body.
new PageImpl<User>(users.subList(start, end), pageable, users.size());
This is the correct answer to pagging a list
public ResponseEntity<User> getUsersByProgramId(
#RequestParam(name = "programId", required = true) Integer programId, Pageable pageable) {
List<User> users = userService.findAllByProgramId(programId);
final int toIndex = Math.min((pageable.getPageNumber() + 1) * pageable.getPageSize(),
bidList.size());
final int fromIndex = Math.max(toIndex - pageable.getPageSize(), 0);
Page<User> pages = new PageImpl<User>(users.subList(fromIndex, toIndex), pageable, users.size());
return new ResponseEntity<>(pages, HttpStatus.OK);
}
u didn't made paged result
new PageImpl<User>(users, pageable, users.size()); does not make paged result implicitly,
in this context, pageable argument just makes meta-data of Page object like page, offset, size... etc
So you have to use Repository method like
Page<User>findAllByProgramId(Integer programId, Pageable pageable);